Merge "Cleanup files and templates using smart sources"
This commit is contained in:
commit
ab5a04ec42
@ -133,6 +133,11 @@ neutron_lbaasv2_user_group: "{{ _neutron_lbaasv2_user_group }}"
|
||||
# 'Overriding OpenStack configuration defaults' in the
|
||||
# 'Advanced configuration' appendix of the Deploy Guide.
|
||||
neutron_api_paste_ini_overrides: {}
|
||||
_neutron_api_paste_ini_overrides:
|
||||
"composite:neutronapi_v2_0":
|
||||
noauth: "cors http_proxy_to_wsgi request_id catch_errors osprofiler extensions neutronapiapp_v2_0"
|
||||
keystone: "cors http_proxy_to_wsgi request_id catch_errors osprofiler authtoken keystonecontext extensions neutronapiapp_v2_0"
|
||||
|
||||
neutron_bgp_dragent_ini_overrides: {}
|
||||
neutron_bgp_dragent_init_overrides: {}
|
||||
neutron_calico_dhcp_agent_ini_overrides: {}
|
||||
@ -168,7 +173,12 @@ neutron_openvswitch_agent_init_overrides: {}
|
||||
# "create_subnet": "rule:admin_or_network_owner"
|
||||
# "get_subnet": "rule:admin_or_owner or rule:shared"
|
||||
neutron_policy_overrides: {}
|
||||
_neutron_rootwrap_conf_overrides:
|
||||
DEFAULT:
|
||||
filters_path: "{{ neutron_conf_dir }}/rootwrap.d,/usr/share/neutron/rootwrap"
|
||||
exec_dirs: "{{ neutron_bin }},/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin"
|
||||
neutron_rootwrap_conf_overrides: {}
|
||||
|
||||
neutron_server_init_overrides: {}
|
||||
neutron_sriov_nic_agent_ini_overrides: {}
|
||||
neutron_sriov_nic_agent_init_overrides: {}
|
||||
|
@ -1,18 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# This is needed because we should ping
|
||||
# from inside a namespace which requires root
|
||||
# _alt variants allow to match -c and -w in any order
|
||||
# (used by NeutronDebugAgent.ping_all)
|
||||
ping: RegExpFilter, ping, root, ping, -w, \d+, -c, \d+, [0-9\.]+
|
||||
ping_alt: RegExpFilter, ping, root, ping, -c, \d+, -w, \d+, [0-9\.]+
|
||||
ping6: RegExpFilter, ping6, root, ping6, -w, \d+, -c, \d+, [0-9A-Fa-f:]+
|
||||
ping6_alt: RegExpFilter, ping6, root, ping6, -c, \d+, -w, \d+, [0-9A-Fa-f:]+
|
@ -1,39 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# dhcp-agent
|
||||
dnsmasq: CommandFilter, dnsmasq, root
|
||||
# dhcp-agent uses kill as well, that's handled by the generic KillFilter
|
||||
# it looks like these are the only signals needed, per
|
||||
# neutron/agent/linux/dhcp.py
|
||||
kill_dnsmasq: KillFilter, root, /sbin/dnsmasq, -9, -HUP, -15
|
||||
kill_dnsmasq_usr: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP, -15
|
||||
|
||||
ovs-vsctl: CommandFilter, ovs-vsctl, root
|
||||
ivs-ctl: CommandFilter, ivs-ctl, root
|
||||
mm-ctl: CommandFilter, mm-ctl, root
|
||||
dhcp_release: CommandFilter, dhcp_release, root
|
||||
dhcp_release6: CommandFilter, dhcp_release6, root
|
||||
|
||||
# haproxy
|
||||
haproxy: RegExpFilter, haproxy, root, haproxy, -f, .*
|
||||
kill_haproxy: KillFilter, root, haproxy, -15, -9, -HUP
|
||||
# RHEL invocation of the metadata proxy will report /usr/bin/python
|
||||
# TODO(dalvarez): Remove kill_metadata* filters in Q release since
|
||||
# neutron-ns-metadata-proxy is now replaced by haproxy. We keep them for now
|
||||
# for the migration process
|
||||
kill_metadata: KillFilter, root, python, -9
|
||||
kill_metadata7: KillFilter, root, python2.7, -9
|
||||
kill_metadata35: KillFilter, root, python3.5, -9
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
@ -1,17 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# Filters for the dibbler-based reference implementation of the pluggable
|
||||
# Prefix Delegation driver. Other implementations using an alternative agent
|
||||
# should include a similar filter in this folder.
|
||||
|
||||
# prefix_delegation_agent
|
||||
dibbler-client: CommandFilter, dibbler-client, root
|
||||
kill_dibbler-client: KillFilter, root, dibbler-client, -9
|
@ -7,5 +7,3 @@
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
ebtables: CommandFilter, ebtables, root
|
@ -1,12 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
# neutron/agent/linux/iptables_firewall.py
|
||||
# "ipset", "-A", ...
|
||||
ipset: CommandFilter, ipset, root
|
@ -1,24 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# neutron/agent/linux/iptables_firewall.py
|
||||
# "iptables-save", ...
|
||||
iptables-save: CommandFilter, iptables-save, root
|
||||
iptables-restore: CommandFilter, iptables-restore, root
|
||||
ip6tables-save: CommandFilter, ip6tables-save, root
|
||||
ip6tables-restore: CommandFilter, ip6tables-restore, root
|
||||
|
||||
# neutron/agent/linux/iptables_firewall.py
|
||||
# "iptables", "-A", ...
|
||||
iptables: CommandFilter, iptables, root
|
||||
ip6tables: CommandFilter, ip6tables, root
|
||||
|
||||
# neutron/agent/linux/ip_conntrack.py
|
||||
conntrack: CommandFilter, conntrack, root
|
@ -1,66 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# arping
|
||||
arping: CommandFilter, arping, root
|
||||
|
||||
# l3_agent
|
||||
sysctl: CommandFilter, sysctl, root
|
||||
route: CommandFilter, route, root
|
||||
radvd: CommandFilter, radvd, root
|
||||
|
||||
# haproxy
|
||||
haproxy: RegExpFilter, haproxy, root, haproxy, -f, .*
|
||||
kill_haproxy: KillFilter, root, haproxy, -15, -9, -HUP
|
||||
# RHEL invocation of the metadata proxy will report /usr/bin/python
|
||||
# TODO(dalvarez): Remove kill_metadata* filters in Q release since
|
||||
# neutron-ns-metadata-proxy is now replaced by haproxy. We keep them for now
|
||||
# for the migration process
|
||||
kill_metadata: KillFilter, root, python, -15, -9
|
||||
kill_metadata7: KillFilter, root, python2.7, -15, -9
|
||||
kill_metadata35: KillFilter, root, python3.5, -15, -9
|
||||
kill_radvd_usr: KillFilter, root, /usr/sbin/radvd, -15, -9, -HUP
|
||||
kill_radvd: KillFilter, root, /sbin/radvd, -15, -9, -HUP
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
||||
|
||||
# l3_tc_lib
|
||||
l3_tc_show_qdisc: RegExpFilter, tc, root, tc, qdisc, show, dev, .+
|
||||
l3_tc_add_qdisc_ingress: RegExpFilter, tc, root, tc, qdisc, add, dev, .+, ingress
|
||||
l3_tc_add_qdisc_egress: RegExpFilter, tc, root, tc, qdisc, add, dev, .+, root, handle, 1:, htb
|
||||
l3_tc_show_filters: RegExpFilter, tc, root, tc, -p, -s, -d, filter, show, dev, .+, parent, .+, prio, 1
|
||||
l3_tc_delete_filters: RegExpFilter, tc, root, tc, filter, del, dev, .+, parent, .+, prio, 1, handle, .+, u32
|
||||
l3_tc_add_filter_ingress: RegExpFilter, tc, root, tc, filter, add, dev, .+, parent, .+, protocol, ip, prio, 1, u32, match, ip, dst, .+, police, rate, .+, burst, .+, drop, flowid, :1
|
||||
l3_tc_add_filter_egress: RegExpFilter, tc, root, tc, filter, add, dev, .+, parent, .+, protocol, ip, prio, 1, u32, match, ip, src, .+, police, rate, .+, burst, .+, drop, flowid, :1
|
||||
|
||||
# For ip monitor
|
||||
kill_ip_monitor: KillFilter, root, ip, -9
|
||||
|
||||
# ovs_lib (if OVSInterfaceDriver is used)
|
||||
ovs-vsctl: CommandFilter, ovs-vsctl, root
|
||||
|
||||
# iptables_manager
|
||||
iptables-save: CommandFilter, iptables-save, root
|
||||
iptables-restore: CommandFilter, iptables-restore, root
|
||||
ip6tables-save: CommandFilter, ip6tables-save, root
|
||||
ip6tables-restore: CommandFilter, ip6tables-restore, root
|
||||
|
||||
# Keepalived
|
||||
keepalived: CommandFilter, keepalived, root
|
||||
kill_keepalived: KillFilter, root, /usr/sbin/keepalived, -HUP, -15, -9
|
||||
|
||||
# l3 agent to delete floatingip's conntrack state
|
||||
conntrack: CommandFilter, conntrack, root
|
||||
|
||||
# keepalived state change monitor
|
||||
keepalived_state_change: CommandFilter, neutron-keepalived-state-change, root
|
@ -1,26 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# haproxy
|
||||
haproxy: CommandFilter, haproxy, root
|
||||
|
||||
# lbaas-agent uses kill as well, that's handled by the generic KillFilter
|
||||
kill_haproxy_usr: KillFilter, root, /usr/sbin/haproxy, -9, -HUP
|
||||
|
||||
ovs-vsctl: CommandFilter, ovs-vsctl, root
|
||||
mm-ctl: CommandFilter, mm-ctl, root
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
||||
route: CommandFilter, route, root
|
||||
|
||||
# arping
|
||||
arping: CommandFilter, arping, root
|
@ -1,29 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# linuxbridge-agent
|
||||
# unclear whether both variants are necessary, but I'm transliterating
|
||||
# from the old mechanism
|
||||
brctl: CommandFilter, brctl, root
|
||||
bridge: CommandFilter, bridge, root
|
||||
sysctl: CommandFilter, sysctl, root
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
||||
|
||||
# tc commands needed for QoS support
|
||||
tc_replace_tbf: RegExpFilter, tc, root, tc, qdisc, replace, dev, .+, root, tbf, rate, .+, latency, .+, burst, .+
|
||||
tc_add_ingress: RegExpFilter, tc, root, tc, qdisc, add, dev, .+, ingress, handle, .+
|
||||
tc_delete: RegExpFilter, tc, root, tc, qdisc, del, dev, .+, .+
|
||||
tc_show_qdisc: RegExpFilter, tc, root, tc, qdisc, show, dev, .+
|
||||
tc_show_filters: RegExpFilter, tc, root, tc, filter, show, dev, .+, parent, .+
|
||||
tc_add_filter: RegExpFilter, tc, root, tc, filter, add, dev, .+, parent, .+, protocol, all, prio, .+, basic, police, rate, .+, burst, .+, mtu, .+, drop
|
@ -1,12 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# netns-cleanup
|
||||
netstat: CommandFilter, netstat, root
|
@ -1,26 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which neutron is
|
||||
# expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# openvswitch-agent
|
||||
# unclear whether both variants are necessary, but I'm transliterating
|
||||
# from the old mechanism
|
||||
ovs-vsctl: CommandFilter, ovs-vsctl, root
|
||||
# NOTE(yamamoto): of_interface=native doesn't use ovs-ofctl
|
||||
ovs-ofctl: CommandFilter, ovs-ofctl, root
|
||||
kill_ovsdb_client: KillFilter, root, /usr/bin/ovsdb-client, -9
|
||||
ovsdb-client: CommandFilter, ovsdb-client, root
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
||||
|
||||
# needed for FDB extension
|
||||
bridge: CommandFilter, bridge, root
|
@ -1,31 +0,0 @@
|
||||
# Command filters to allow privsep daemon to be started via rootwrap.
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
[Filters]
|
||||
|
||||
# By installing the following, the local admin is asserting that:
|
||||
#
|
||||
# 1. The python module load path used by privsep-helper
|
||||
# command as root (as started by sudo/rootwrap) is trusted.
|
||||
# 2. Any oslo.config files matching the --config-file
|
||||
# arguments below are trusted.
|
||||
# 3. Users allowed to run sudo/rootwrap with this configuration(*) are
|
||||
# also allowed to invoke python "entrypoint" functions from
|
||||
# --privsep_context with the additional (possibly root) privileges
|
||||
# configured for that context.
|
||||
#
|
||||
# (*) ie: the user is allowed by /etc/sudoers to run rootwrap as root
|
||||
#
|
||||
# In particular, the oslo.config and python module path must not
|
||||
# be writeable by the unprivileged user.
|
||||
|
||||
# oslo.privsep default neutron context
|
||||
privsep: PathFilter, privsep-helper, root,
|
||||
--config-file, /etc,
|
||||
--privsep_context, neutron.privileged.default,
|
||||
--privsep_sock_path, /
|
||||
|
||||
# NOTE: A second `--config-file` arg can also be added above. Since
|
||||
# many neutron components are installed like that (eg: by devstack).
|
||||
# Adjust to suit local requirements.
|
@ -45,24 +45,6 @@
|
||||
when: "'neutron-metadata-agent' in (filtered_neutron_services | map(attribute='service_key') | list)"
|
||||
listen: "Restart neutron services"
|
||||
|
||||
# Note (odyssey4me):
|
||||
# The policy.json file is currently read continually by the services
|
||||
# and is not only read on service start. We therefore cannot template
|
||||
# directly to the file read by the service because the new policies
|
||||
# may not be valid until the service restarts. This is particularly
|
||||
# important during a major upgrade. We therefore only put the policy
|
||||
# file in place after the service has been stopped.
|
||||
#
|
||||
- name: Copy new policy file into place
|
||||
copy:
|
||||
src: "{{ neutron_conf_dir }}/policy.json-{{ neutron_venv_tag }}"
|
||||
dest: "{{ neutron_conf_dir }}/policy.json"
|
||||
owner: "root"
|
||||
group: "{{ neutron_system_group_name }}"
|
||||
mode: "0640"
|
||||
remote_src: yes
|
||||
listen: "Restart neutron services"
|
||||
|
||||
- name: Perform a DB contract
|
||||
command: "{{ neutron_bin }}/neutron-db-manage upgrade --contract"
|
||||
become: yes
|
||||
|
@ -13,6 +13,35 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
- name: Create plugins neutron dir
|
||||
file:
|
||||
path: "{{ item.path | default(omit) }}"
|
||||
state: "directory"
|
||||
owner: "{{ item.owner|default(neutron_system_user_name) }}"
|
||||
group: "{{ item.group|default(neutron_system_group_name) }}"
|
||||
mode: "{{ item.mode | default(omit) }}"
|
||||
with_items:
|
||||
- path: "{{ neutron_conf_dir }}/plugins"
|
||||
mode: "0750"
|
||||
- path: "{{ neutron_conf_dir }}/plugins/{{ neutron_plugin_type.split('.')[0] }}"
|
||||
mode: "0750"
|
||||
- path: "{{ neutron_conf_dir }}/rootwrap.d"
|
||||
owner: "root"
|
||||
group: "root"
|
||||
|
||||
# NOTE(cloudnull): This task is required to copy rootwrap filters that we need
|
||||
# and neutron does not provide by default.
|
||||
- name: Copy extra neutron rootwrap filters
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: "{{ neutron_conf_dir }}/rootwrap.d/"
|
||||
owner: "root"
|
||||
group: "root"
|
||||
with_fileglob:
|
||||
- rootwrap.d/*
|
||||
notify:
|
||||
- Restart neutron services
|
||||
|
||||
- name: Copy common neutron config
|
||||
config_template:
|
||||
src: "{{ item.src }}"
|
||||
@ -31,21 +60,54 @@
|
||||
dest: "{{ neutron_conf_dir }}/{{ neutron_plugins[neutron_plugin_type].plugin_ini }}"
|
||||
config_overrides: "{{ neutron_plugins[neutron_plugin_type].plugin_conf_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
- src: "api-paste.ini.j2"
|
||||
dest: "{{ neutron_conf_dir }}/api-paste.ini"
|
||||
config_overrides: "{{ neutron_api_paste_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
- src: "rootwrap.conf.j2"
|
||||
dest: "{{ neutron_conf_dir }}/rootwrap.conf"
|
||||
config_overrides: "{{ neutron_rootwrap_conf_overrides }}"
|
||||
config_type: "ini"
|
||||
- src: "policy.json.j2"
|
||||
dest: "{{ neutron_conf_dir }}/policy.json-{{ neutron_venv_tag }}"
|
||||
config_overrides: "{{ neutron_policy_overrides }}"
|
||||
config_type: "json"
|
||||
notify:
|
||||
- Restart neutron services
|
||||
|
||||
- name: Preserve original configuration file(s)
|
||||
command: "cp {{ item.target_f }} {{ item.target_f }}.original"
|
||||
args:
|
||||
creates: "{{ item.target_f }}.original"
|
||||
with_items: "{{ neutron_core_files }}"
|
||||
|
||||
- name: Fetch override files
|
||||
fetch:
|
||||
src: "{{ item.target_f }}.original"
|
||||
dest: "{{ item.tmp_f }}"
|
||||
flat: yes
|
||||
changed_when: false
|
||||
with_items: "{{ neutron_core_files }}"
|
||||
run_once: true
|
||||
|
||||
- name: Copy common neutron config
|
||||
config_template:
|
||||
src: "{{ item.tmp_f }}"
|
||||
dest: "{{ item.target_f }}"
|
||||
owner: "{{ item.owner | default('root') }}"
|
||||
group: "{{ item.group | default(neutron_system_group_name) }}"
|
||||
mode: "{{ item.mode | default('0640') }}"
|
||||
config_overrides: "{{ item.config_overrides }}"
|
||||
config_type: "{{ item.config_type }}"
|
||||
with_items: "{{ neutron_core_files }}"
|
||||
notify:
|
||||
- Restart neutron services
|
||||
|
||||
- name: Cleanup fetched temp files
|
||||
file:
|
||||
path: "{{ item.tmp_f }}"
|
||||
state: absent
|
||||
changed_when: false
|
||||
delegate_to: localhost
|
||||
with_items: "{{ neutron_core_files }}"
|
||||
|
||||
# NOTE(cloudnull): This will ensure strong permissions on all rootwrap files.
|
||||
- name: Set rootwrap.d permissions
|
||||
file:
|
||||
path: "{{ neutron_conf_dir }}/rootwrap.d"
|
||||
owner: "root"
|
||||
group: "root"
|
||||
mode: "0640"
|
||||
recurse: true
|
||||
|
||||
- name: Copy neutron ml2 plugin config
|
||||
config_template:
|
||||
src: "{{ neutron_plugins[item].plugin_ini }}.j2"
|
||||
@ -97,29 +159,6 @@
|
||||
when:
|
||||
- "'bgpvpn' in neutron_plugin_base"
|
||||
|
||||
- name: Copy neutron rootwrap filters
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: "{{ neutron_conf_dir }}/rootwrap.d/"
|
||||
owner: "root"
|
||||
group: "root"
|
||||
with_fileglob:
|
||||
- rootwrap.d/*
|
||||
notify:
|
||||
- Restart neutron services
|
||||
|
||||
- name: Drop neutron agent rootwrap filters
|
||||
copy:
|
||||
src: "{{ item.service_rootwrap }}"
|
||||
dest: "{{ neutron_conf_dir }}/{{ item.service_rootwrap }}"
|
||||
owner: "root"
|
||||
group: "root"
|
||||
with_items: "{{ filtered_neutron_services }}"
|
||||
when:
|
||||
- "'service_rootwrap' in item"
|
||||
notify:
|
||||
- Restart neutron services
|
||||
|
||||
# NOTE: Remove this in S
|
||||
# This option has been removed with the implementation of networkd within the
|
||||
# host and container. Additionally the execution of this script is now
|
||||
|
@ -29,25 +29,63 @@
|
||||
createhome: "yes"
|
||||
home: "/var/lib/{{ neutron_system_user_name }}"
|
||||
|
||||
# NOTE(cloudnull): During an upgrade the local directory may exist on a source
|
||||
# install. If the directory does exist it will need to be
|
||||
# removed. This is required on source installs because the
|
||||
# config directory is a link.
|
||||
- name: Source config block
|
||||
block:
|
||||
- name: Stat config directory
|
||||
stat:
|
||||
path: "{{ neutron_conf_dir }}"
|
||||
register: neutron_conf_dir_stat
|
||||
|
||||
- name: Remove the config directory
|
||||
file:
|
||||
path: "{{ neutron_conf_dir }}"
|
||||
state: absent
|
||||
when:
|
||||
- neutron_conf_dir_stat.stat.isdir is defined and
|
||||
neutron_conf_dir_stat.stat.isdir
|
||||
when:
|
||||
- neutron_install_method == 'source'
|
||||
|
||||
- name: Create neutron dir
|
||||
file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
path: "{{ item.path | default(omit) }}"
|
||||
src: "{{ item.src | default(omit) }}"
|
||||
dest: "{{ item.dest | default(omit) }}"
|
||||
state: "{{ item.state | default('directory') }}"
|
||||
owner: "{{ item.owner|default(neutron_system_user_name) }}"
|
||||
group: "{{ item.group|default(neutron_system_group_name) }}"
|
||||
mode: "{{ item.mode | default(omit) }}"
|
||||
force: "{{ item.force | default(omit) }}"
|
||||
when:
|
||||
- (item.condition | default(true)) | bool
|
||||
with_items:
|
||||
- { path: "/openstack", owner: "root", group: "root" }
|
||||
- { path: "{{ neutron_conf_dir }}", mode: "0750" }
|
||||
- { path: "{{ neutron_conf_dir }}/plugins", mode: "0750" }
|
||||
- { path: "{{ neutron_conf_dir }}/plugins/{{ neutron_plugin_type.split('.')[0] }}", mode: "0750" }
|
||||
- { path: "{{ neutron_conf_dir }}/rootwrap.d", owner: "root", group: "root" }
|
||||
- { path: "/etc/sudoers.d", mode: "0750", owner: "root", group: "root" }
|
||||
- { path: "/var/cache/neutron" }
|
||||
- { path: "{{ neutron_lock_path }}" }
|
||||
- { path: "/var/run/neutron" }
|
||||
- { path: "{{ neutron_system_home_folder }}", mode: "0755" }
|
||||
- { path: "{{ neutron_system_home_folder }}/ha_confs" }
|
||||
- path: "/openstack"
|
||||
owner: "root"
|
||||
group: "root"
|
||||
- path: "{{ (neutron_install_method == 'distro') | ternary(neutron_conf_dir, (neutron_bin | dirname) + '/etc/neutron') }}"
|
||||
mode: "0750"
|
||||
# NOTE(cloudnull): The "src" path is relative. This ensures all files remain
|
||||
# within the host/container confines when connecting to
|
||||
# them using the connection plugin or the root filesystem.
|
||||
- dest: "{{ neutron_conf_dir }}"
|
||||
src: "{{ neutron_bin | dirname | regex_replace('^/', '../') }}/etc/neutron"
|
||||
state: link
|
||||
force: true
|
||||
condition: "{{ neutron_install_method == 'source' }}"
|
||||
- path: "/etc/sudoers.d"
|
||||
mode: "0750"
|
||||
owner: "root"
|
||||
group: "root"
|
||||
- path: "/var/cache/neutron"
|
||||
- path: "{{ neutron_lock_path }}"
|
||||
- path: "/var/run/neutron"
|
||||
- path: "{{ neutron_system_home_folder }}"
|
||||
mode: "0755"
|
||||
- path: "{{ neutron_system_home_folder }}/ha_confs"
|
||||
|
||||
- name: Test for log directory or link
|
||||
shell: |
|
||||
|
@ -1,45 +0,0 @@
|
||||
[composite:neutron]
|
||||
use = egg:Paste#urlmap
|
||||
/: neutronversions_composite
|
||||
/v2.0: neutronapi_v2_0
|
||||
|
||||
[composite:neutronapi_v2_0]
|
||||
use = call:neutron.auth:pipeline_factory
|
||||
noauth = cors http_proxy_to_wsgi request_id catch_errors osprofiler extensions neutronapiapp_v2_0
|
||||
keystone = cors http_proxy_to_wsgi request_id catch_errors osprofiler authtoken keystonecontext extensions neutronapiapp_v2_0
|
||||
|
||||
[composite:neutronversions_composite]
|
||||
use = call:neutron.auth:pipeline_factory
|
||||
noauth = cors http_proxy_to_wsgi neutronversions
|
||||
keystone = cors http_proxy_to_wsgi neutronversions
|
||||
|
||||
[filter:request_id]
|
||||
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||
|
||||
[filter:catch_errors]
|
||||
paste.filter_factory = oslo_middleware:CatchErrors.factory
|
||||
|
||||
[filter:cors]
|
||||
paste.filter_factory = oslo_middleware.cors:filter_factory
|
||||
oslo_config_project = neutron
|
||||
|
||||
[filter:http_proxy_to_wsgi]
|
||||
paste.filter_factory = oslo_middleware.http_proxy_to_wsgi:HTTPProxyToWSGI.factory
|
||||
|
||||
[filter:keystonecontext]
|
||||
paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory
|
||||
|
||||
[filter:authtoken]
|
||||
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
|
||||
|
||||
[filter:extensions]
|
||||
paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory
|
||||
|
||||
[app:neutronversions]
|
||||
paste.app_factory = neutron.pecan_wsgi.app:versions_factory
|
||||
|
||||
[app:neutronapiapp_v2_0]
|
||||
paste.app_factory = neutron.api.v2.router:APIRouter.factory
|
||||
|
||||
[filter:osprofiler]
|
||||
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
|
@ -1,235 +0,0 @@
|
||||
{
|
||||
"context_is_admin": "role:admin",
|
||||
"owner": "tenant_id:%(tenant_id)s",
|
||||
"admin_or_owner": "rule:context_is_admin or rule:owner",
|
||||
"context_is_advsvc": "role:advsvc",
|
||||
"admin_or_network_owner": "rule:context_is_admin or tenant_id:%(network:tenant_id)s",
|
||||
"admin_owner_or_network_owner": "rule:owner or rule:admin_or_network_owner",
|
||||
"admin_only": "rule:context_is_admin",
|
||||
"regular_user": "",
|
||||
"admin_or_data_plane_int": "rule:context_is_admin or role:data_plane_integrator",
|
||||
"shared": "field:networks:shared=True",
|
||||
"shared_subnetpools": "field:subnetpools:shared=True",
|
||||
"shared_address_scopes": "field:address_scopes:shared=True",
|
||||
"external": "field:networks:router:external=True",
|
||||
"default": "rule:admin_or_owner",
|
||||
|
||||
"create_subnet": "rule:admin_or_network_owner",
|
||||
"create_subnet:segment_id": "rule:admin_only",
|
||||
"create_subnet:service_types": "rule:admin_only",
|
||||
"get_subnet": "rule:admin_or_owner or rule:shared",
|
||||
"get_subnet:segment_id": "rule:admin_only",
|
||||
"update_subnet": "rule:admin_or_network_owner",
|
||||
"update_subnet:service_types": "rule:admin_only",
|
||||
"delete_subnet": "rule:admin_or_network_owner",
|
||||
|
||||
"create_subnetpool": "",
|
||||
"create_subnetpool:shared": "rule:admin_only",
|
||||
"create_subnetpool:is_default": "rule:admin_only",
|
||||
"get_subnetpool": "rule:admin_or_owner or rule:shared_subnetpools",
|
||||
"update_subnetpool": "rule:admin_or_owner",
|
||||
"update_subnetpool:is_default": "rule:admin_only",
|
||||
"delete_subnetpool": "rule:admin_or_owner",
|
||||
|
||||
"create_address_scope": "",
|
||||
"create_address_scope:shared": "rule:admin_only",
|
||||
"get_address_scope": "rule:admin_or_owner or rule:shared_address_scopes",
|
||||
"update_address_scope": "rule:admin_or_owner",
|
||||
"update_address_scope:shared": "rule:admin_only",
|
||||
"delete_address_scope": "rule:admin_or_owner",
|
||||
|
||||
"create_network": "",
|
||||
"get_network": "rule:admin_or_owner or rule:shared or rule:external or rule:context_is_advsvc",
|
||||
"get_network:router:external": "rule:regular_user",
|
||||
"get_network:segments": "rule:admin_only",
|
||||
"get_network:provider:network_type": "rule:admin_only",
|
||||
"get_network:provider:physical_network": "rule:admin_only",
|
||||
"get_network:provider:segmentation_id": "rule:admin_only",
|
||||
"get_network:queue_id": "rule:admin_only",
|
||||
"get_network_ip_availabilities": "rule:admin_only",
|
||||
"get_network_ip_availability": "rule:admin_only",
|
||||
"create_network:shared": "rule:admin_only",
|
||||
"create_network:router:external": "rule:admin_only",
|
||||
"create_network:is_default": "rule:admin_only",
|
||||
"create_network:segments": "rule:admin_only",
|
||||
"create_network:provider:network_type": "rule:admin_only",
|
||||
"create_network:provider:physical_network": "rule:admin_only",
|
||||
"create_network:provider:segmentation_id": "rule:admin_only",
|
||||
"update_network": "rule:admin_or_owner",
|
||||
"update_network:segments": "rule:admin_only",
|
||||
"update_network:shared": "rule:admin_only",
|
||||
"update_network:provider:network_type": "rule:admin_only",
|
||||
"update_network:provider:physical_network": "rule:admin_only",
|
||||
"update_network:provider:segmentation_id": "rule:admin_only",
|
||||
"update_network:router:external": "rule:admin_only",
|
||||
"delete_network": "rule:admin_or_owner",
|
||||
|
||||
"create_segment": "rule:admin_only",
|
||||
"get_segment": "rule:admin_only",
|
||||
"update_segment": "rule:admin_only",
|
||||
"delete_segment": "rule:admin_only",
|
||||
|
||||
"network_device": "field:port:device_owner=~^network:",
|
||||
"create_port": "",
|
||||
"create_port:device_owner": "not rule:network_device or rule:context_is_advsvc or rule:admin_or_network_owner",
|
||||
"create_port:mac_address": "rule:context_is_advsvc or rule:admin_or_network_owner",
|
||||
"create_port:fixed_ips:ip_address": "rule:context_is_advsvc or rule:admin_or_network_owner",
|
||||
"create_port:fixed_ips:subnet_id": "rule:context_is_advsvc or rule:admin_or_network_owner or rule:shared",
|
||||
"create_port:port_security_enabled": "rule:context_is_advsvc or rule:admin_or_network_owner",
|
||||
"create_port:binding:host_id": "rule:admin_only",
|
||||
"create_port:binding:profile": "rule:admin_only",
|
||||
"create_port:mac_learning_enabled": "rule:context_is_advsvc or rule:admin_or_network_owner",
|
||||
"create_port:allowed_address_pairs": "rule:admin_or_network_owner",
|
||||
"get_port": "rule:context_is_advsvc or rule:admin_owner_or_network_owner",
|
||||
"get_port:queue_id": "rule:admin_only",
|
||||
"get_port:binding:vif_type": "rule:admin_only",
|
||||
"get_port:binding:vif_details": "rule:admin_only",
|
||||
"get_port:binding:host_id": "rule:admin_only",
|
||||
"get_port:binding:profile": "rule:admin_only",
|
||||
"update_port": "rule:admin_or_owner or rule:context_is_advsvc",
|
||||
"update_port:device_owner": "not rule:network_device or rule:context_is_advsvc or rule:admin_or_network_owner",
|
||||
"update_port:mac_address": "rule:admin_only or rule:context_is_advsvc",
|
||||
"update_port:fixed_ips:ip_address": "rule:context_is_advsvc or rule:admin_or_network_owner",
|
||||
"update_port:fixed_ips:subnet_id": "rule:context_is_advsvc or rule:admin_or_network_owner or rule:shared",
|
||||
"update_port:port_security_enabled": "rule:context_is_advsvc or rule:admin_or_network_owner",
|
||||
"update_port:binding:host_id": "rule:admin_only",
|
||||
"update_port:binding:profile": "rule:admin_only",
|
||||
"update_port:mac_learning_enabled": "rule:context_is_advsvc or rule:admin_or_network_owner",
|
||||
"update_port:allowed_address_pairs": "rule:admin_or_network_owner",
|
||||
"update_port:data_plane_status": "rule:admin_or_data_plane_int",
|
||||
"delete_port": "rule:context_is_advsvc or rule:admin_owner_or_network_owner",
|
||||
|
||||
"get_router:ha": "rule:admin_only",
|
||||
"create_router": "rule:regular_user",
|
||||
"create_router:external_gateway_info:enable_snat": "rule:admin_only",
|
||||
"create_router:distributed": "rule:admin_only",
|
||||
"create_router:ha": "rule:admin_only",
|
||||
"get_router": "rule:admin_or_owner",
|
||||
"get_router:distributed": "rule:admin_only",
|
||||
"update_router": "rule:admin_or_owner",
|
||||
"update_router:external_gateway_info": "rule:admin_or_owner",
|
||||
"update_router:external_gateway_info:network_id": "rule:admin_or_owner",
|
||||
"update_router:external_gateway_info:enable_snat": "rule:admin_only",
|
||||
"update_router:distributed": "rule:admin_only",
|
||||
"update_router:ha": "rule:admin_only",
|
||||
"delete_router": "rule:admin_or_owner",
|
||||
|
||||
"add_router_interface": "rule:admin_or_owner",
|
||||
"remove_router_interface": "rule:admin_or_owner",
|
||||
|
||||
"create_router:external_gateway_info:external_fixed_ips": "rule:admin_only",
|
||||
"update_router:external_gateway_info:external_fixed_ips": "rule:admin_only",
|
||||
|
||||
"create_qos_queue": "rule:admin_only",
|
||||
"get_qos_queue": "rule:admin_only",
|
||||
|
||||
"update_agent": "rule:admin_only",
|
||||
"delete_agent": "rule:admin_only",
|
||||
"get_agent": "rule:admin_only",
|
||||
|
||||
"create_dhcp-network": "rule:admin_only",
|
||||
"delete_dhcp-network": "rule:admin_only",
|
||||
"get_dhcp-networks": "rule:admin_only",
|
||||
"create_l3-router": "rule:admin_only",
|
||||
"delete_l3-router": "rule:admin_only",
|
||||
"get_l3-routers": "rule:admin_only",
|
||||
"get_dhcp-agents": "rule:admin_only",
|
||||
"get_l3-agents": "rule:admin_only",
|
||||
"get_loadbalancer-agent": "rule:admin_only",
|
||||
"get_loadbalancer-pools": "rule:admin_only",
|
||||
"get_agent-loadbalancers": "rule:admin_only",
|
||||
"get_loadbalancer-hosting-agent": "rule:admin_only",
|
||||
|
||||
"create_floatingip": "rule:regular_user",
|
||||
"create_floatingip:floating_ip_address": "rule:admin_only",
|
||||
"update_floatingip": "rule:admin_or_owner",
|
||||
"delete_floatingip": "rule:admin_or_owner",
|
||||
"get_floatingip": "rule:admin_or_owner",
|
||||
|
||||
"create_network_profile": "rule:admin_only",
|
||||
"update_network_profile": "rule:admin_only",
|
||||
"delete_network_profile": "rule:admin_only",
|
||||
"get_network_profiles": "",
|
||||
"get_network_profile": "",
|
||||
"update_policy_profiles": "rule:admin_only",
|
||||
"get_policy_profiles": "",
|
||||
"get_policy_profile": "",
|
||||
|
||||
"create_metering_label": "rule:admin_only",
|
||||
"delete_metering_label": "rule:admin_only",
|
||||
"get_metering_label": "rule:admin_only",
|
||||
|
||||
"create_metering_label_rule": "rule:admin_only",
|
||||
"delete_metering_label_rule": "rule:admin_only",
|
||||
"get_metering_label_rule": "rule:admin_only",
|
||||
|
||||
"get_service_provider": "rule:regular_user",
|
||||
"get_lsn": "rule:admin_only",
|
||||
"create_lsn": "rule:admin_only",
|
||||
|
||||
"create_flavor": "rule:admin_only",
|
||||
"update_flavor": "rule:admin_only",
|
||||
"delete_flavor": "rule:admin_only",
|
||||
"get_flavors": "rule:regular_user",
|
||||
"get_flavor": "rule:regular_user",
|
||||
"create_service_profile": "rule:admin_only",
|
||||
"update_service_profile": "rule:admin_only",
|
||||
"delete_service_profile": "rule:admin_only",
|
||||
"get_service_profiles": "rule:admin_only",
|
||||
"get_service_profile": "rule:admin_only",
|
||||
|
||||
"get_policy": "rule:regular_user",
|
||||
"create_policy": "rule:admin_only",
|
||||
"update_policy": "rule:admin_only",
|
||||
"delete_policy": "rule:admin_only",
|
||||
"get_policy_bandwidth_limit_rule": "rule:regular_user",
|
||||
"create_policy_bandwidth_limit_rule": "rule:admin_only",
|
||||
"delete_policy_bandwidth_limit_rule": "rule:admin_only",
|
||||
"update_policy_bandwidth_limit_rule": "rule:admin_only",
|
||||
"get_policy_dscp_marking_rule": "rule:regular_user",
|
||||
"create_policy_dscp_marking_rule": "rule:admin_only",
|
||||
"delete_policy_dscp_marking_rule": "rule:admin_only",
|
||||
"update_policy_dscp_marking_rule": "rule:admin_only",
|
||||
"get_rule_type": "rule:regular_user",
|
||||
"get_policy_minimum_bandwidth_rule": "rule:regular_user",
|
||||
"create_policy_minimum_bandwidth_rule": "rule:admin_only",
|
||||
"delete_policy_minimum_bandwidth_rule": "rule:admin_only",
|
||||
"update_policy_minimum_bandwidth_rule": "rule:admin_only",
|
||||
|
||||
"restrict_wildcard": "(not field:rbac_policy:target_tenant=*) or rule:admin_only",
|
||||
"create_rbac_policy": "",
|
||||
"create_rbac_policy:target_tenant": "rule:restrict_wildcard",
|
||||
"update_rbac_policy": "rule:admin_or_owner",
|
||||
"update_rbac_policy:target_tenant": "rule:restrict_wildcard and rule:admin_or_owner",
|
||||
"get_rbac_policy": "rule:admin_or_owner",
|
||||
"delete_rbac_policy": "rule:admin_or_owner",
|
||||
|
||||
"create_flavor_service_profile": "rule:admin_only",
|
||||
"delete_flavor_service_profile": "rule:admin_only",
|
||||
"get_flavor_service_profile": "rule:regular_user",
|
||||
"get_auto_allocated_topology": "rule:admin_or_owner",
|
||||
|
||||
"create_trunk": "rule:regular_user",
|
||||
"get_trunk": "rule:admin_or_owner",
|
||||
"delete_trunk": "rule:admin_or_owner",
|
||||
"get_subports": "",
|
||||
"add_subports": "rule:admin_or_owner",
|
||||
"remove_subports": "rule:admin_or_owner",
|
||||
|
||||
"get_security_groups": "rule:admin_or_owner",
|
||||
"get_security_group": "rule:admin_or_owner",
|
||||
"create_security_group": "rule:admin_or_owner",
|
||||
"update_security_group": "rule:admin_or_owner",
|
||||
"delete_security_group": "rule:admin_or_owner",
|
||||
"get_security_group_rules": "rule:admin_or_owner",
|
||||
"get_security_group_rule": "rule:admin_or_owner",
|
||||
"create_security_group_rule": "rule:admin_or_owner",
|
||||
"delete_security_group_rule": "rule:admin_or_owner",
|
||||
|
||||
"get_loggable_resources": "rule:admin_only",
|
||||
"create_log": "rule:admin_only",
|
||||
"update_log": "rule:admin_only",
|
||||
"delete_log": "rule:admin_only",
|
||||
"get_logs": "rule:admin_only",
|
||||
"get_log": "rule:admin_only"
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
# Configuration for neutron-rootwrap
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
[DEFAULT]
|
||||
# List of directories to load filter definitions from (separated by ',').
|
||||
# These directories MUST all be only writeable by root !
|
||||
filters_path={{ neutron_conf_dir }}/rootwrap.d,/usr/share/neutron/rootwrap
|
||||
|
||||
# List of directories to search executables in, in case filters do not
|
||||
# explicitely specify a full path (separated by ',')
|
||||
# If not specified, defaults to system PATH environment variable.
|
||||
# These directories MUST all be only writeable by root !
|
||||
exec_dirs={{ neutron_bin }},/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin
|
||||
|
||||
# Enable logging to syslog
|
||||
# Default value is False
|
||||
use_syslog=False
|
||||
|
||||
# Which syslog facility to use.
|
||||
# Valid values include auth, authpriv, syslog, local0, local1...
|
||||
# Default value is 'syslog'
|
||||
syslog_log_facility=syslog
|
||||
|
||||
# Which messages to log.
|
||||
# INFO means log all usage
|
||||
# ERROR means only log unsuccessful attempts
|
||||
syslog_log_level=ERROR
|
||||
|
||||
[xenapi]
|
||||
# XenAPI configuration is only required by the L2 agent if it is to
|
||||
# target a XenServer/XCP compute host's dom0.
|
||||
xenapi_connection_url=<None>
|
||||
xenapi_connection_username=root
|
||||
xenapi_connection_password=<None>
|
@ -478,3 +478,24 @@ neutron_driver_quota: neutron.db.quota.driver.DbQuotaDriver
|
||||
# pip packages required by this role. The value is picked up
|
||||
# by the py_pkgs lookup.
|
||||
neutron_role_project_group: neutron_all
|
||||
|
||||
###
|
||||
### Internals: files central to neutron we can override
|
||||
###
|
||||
|
||||
neutron_core_files:
|
||||
- tmp_f: "/tmp/api-paste.ini.original"
|
||||
target_f: "{{ neutron_conf_dir }}/api-paste.ini"
|
||||
config_overrides: "{{ _neutron_api_paste_ini_overrides | combine(neutron_api_paste_ini_overrides, recursive=True) }}"
|
||||
config_type: "ini"
|
||||
- tmp_f: "/tmp/rootwrap.conf.original"
|
||||
target_f: "{{ neutron_conf_dir }}/rootwrap.conf"
|
||||
config_overrides: "{{ _neutron_rootwrap_conf_overrides | combine(neutron_rootwrap_conf_overrides, recursive=True) }}"
|
||||
config_type: "ini"
|
||||
owner: "root"
|
||||
group: "{{ neutron_system_group_name }}"
|
||||
mode: "0640"
|
||||
- tmp_f: "/tmp/policy.json.original"
|
||||
target_f: "{{ neutron_conf_dir }}/policy.json"
|
||||
config_overrides: "{{ neutron_policy_overrides }}"
|
||||
config_type: "json"
|
||||
|
Loading…
x
Reference in New Issue
Block a user