d3daa09bc4
fwaas_v2 extension is added in l3_agent.ini by default from stein. However with the newly introduced neutron-api configuration parameter enable-fwaas, the extension have to added only when it is set to true on neutron-api charm. neutron-api charm adds enabled-fwaas as relation data in the commit https://review.opendev.org/c/openstack/charm-neutron-api/+/806676. This patch removes special handling of fwaas_v2 as l3_extension_plugins is already updated in relation data based on neutron-api enable-fwaas configuration. Add python3-neutron-fwaas in py3 package list only for rocky release. The package is already part of dependency of neutron-l3-agent until Ussuri release and the dependency will be removed from Victoria release in package control files. Synced charmhelpers to get related changes https://github.com/juju/charm-helpers/pull/635 Closes-Bug: #1934129 Change-Id: I1546f45635bfd7af76001c1df4f99d219a9d8039
428 lines
14 KiB
Python
428 lines
14 KiB
Python
# Copyright 2016 Canonical Ltd
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import os
|
|
import uuid
|
|
from charmhelpers.core.hookenv import (
|
|
config,
|
|
log,
|
|
relation_get,
|
|
relation_ids,
|
|
related_units,
|
|
unit_get,
|
|
network_get_primary_address,
|
|
)
|
|
from charmhelpers.core.host import (
|
|
CompareHostReleases,
|
|
is_container,
|
|
lsb_release,
|
|
write_file,
|
|
)
|
|
from charmhelpers.contrib.openstack import context
|
|
from charmhelpers.contrib.openstack.utils import (
|
|
get_host_ip,
|
|
)
|
|
from charmhelpers.contrib.network.ip import (
|
|
get_address_in_network,
|
|
)
|
|
from charmhelpers.contrib.openstack.context import (
|
|
OSContextGenerator,
|
|
NeutronAPIContext,
|
|
)
|
|
from charmhelpers.contrib.openstack.utils import (
|
|
os_release,
|
|
CompareOpenStackReleases,
|
|
)
|
|
|
|
IPTABLES_HYBRID = 'iptables_hybrid'
|
|
OPENVSWITCH = 'openvswitch'
|
|
VALID_FIREWALL_DRIVERS = (IPTABLES_HYBRID, OPENVSWITCH)
|
|
|
|
NFG_LOG_RATE_LIMIT_MIN = 100
|
|
NFG_LOG_BURST_LIMIT_MIN = 25
|
|
|
|
|
|
def _get_firewall_driver(ovs_ctxt):
|
|
'''
|
|
Determine the firewall driver to use based on configuration,
|
|
OpenStack and Ubuntu releases.
|
|
|
|
@returns str: firewall driver to use for OpenvSwitch
|
|
'''
|
|
driver = config('firewall-driver') or IPTABLES_HYBRID
|
|
release = lsb_release()['DISTRIB_CODENAME']
|
|
if driver not in VALID_FIREWALL_DRIVERS:
|
|
return IPTABLES_HYBRID
|
|
|
|
if driver == IPTABLES_HYBRID and ovs_ctxt['enable_nsg_logging']:
|
|
msg = "NSG logging can not be enabled - need to set " \
|
|
"firewall driver to 'openvswitch' explicitly"
|
|
log(msg, "WARN")
|
|
|
|
if (driver == OPENVSWITCH and
|
|
CompareHostReleases(release) < 'xenial'):
|
|
# NOTE(jamespage): Switch back to iptables_hybrid for
|
|
# Ubuntu releases prior to Xenial due
|
|
# to requirements for Linux >= 4.4 and
|
|
# Open vSwitch >= 2.5
|
|
return IPTABLES_HYBRID
|
|
|
|
return driver
|
|
|
|
|
|
def get_nsg_log_path(desired_nsg_log_path):
|
|
if not desired_nsg_log_path:
|
|
# None means "we need to use syslog" - no need
|
|
# to check anything on filesystem
|
|
return None
|
|
|
|
dst_dir, _ = os.path.split(desired_nsg_log_path)
|
|
path_exists = os.path.exists(dst_dir)
|
|
if not path_exists:
|
|
log(
|
|
"Desired NSG log directory {} not exists! "
|
|
"falling back to syslog".format(dst_dir),
|
|
"WARN"
|
|
)
|
|
return None
|
|
|
|
if path_exists and os.path.isdir(desired_nsg_log_path):
|
|
log(
|
|
"Desired NSG log path {} should be file, not directory! "
|
|
"falling back to syslog".format(desired_nsg_log_path),
|
|
"WARN"
|
|
)
|
|
return None
|
|
|
|
return desired_nsg_log_path
|
|
|
|
|
|
def validate_nfg_log_path(desired_nfg_log_path):
|
|
if not desired_nfg_log_path:
|
|
# None means "we need to use syslog" - no need
|
|
# to check anything on filesystem
|
|
return None
|
|
|
|
dst_dir, _ = os.path.split(desired_nfg_log_path)
|
|
path_exists = os.path.exists(dst_dir)
|
|
if not path_exists:
|
|
log(
|
|
"Desired NFG log directory {} not exists! "
|
|
"falling back to syslog".format(dst_dir),
|
|
"WARN"
|
|
)
|
|
return None
|
|
|
|
if path_exists and os.path.isdir(desired_nfg_log_path):
|
|
log(
|
|
"Desired NFG log path {} should be file, not directory! "
|
|
"falling back to syslog".format(desired_nfg_log_path),
|
|
"WARN"
|
|
)
|
|
return None
|
|
|
|
return desired_nfg_log_path
|
|
|
|
|
|
class OVSPluginContext(context.NeutronContext):
|
|
interfaces = []
|
|
|
|
@property
|
|
def plugin(self):
|
|
return 'ovs'
|
|
|
|
@property
|
|
def network_manager(self):
|
|
return 'neutron'
|
|
|
|
@property
|
|
def neutron_security_groups(self):
|
|
if config('disable-security-groups'):
|
|
return False
|
|
neutron_api_settings = NeutronAPIContext()()
|
|
return neutron_api_settings['neutron_security_groups']
|
|
|
|
def disable_mlockall(self):
|
|
'''
|
|
Determine if Open vSwitch use of mlockall() should be disabled
|
|
|
|
If the disable-mlockall config option is unset, mlockall will be
|
|
disabled if running in a container and will default to enabled if
|
|
not running in a container.
|
|
'''
|
|
disable_mlockall = config('disable-mlockall')
|
|
if disable_mlockall is None:
|
|
disable_mlockall = False
|
|
if is_container():
|
|
disable_mlockall = True
|
|
cmp_release = CompareOpenStackReleases(
|
|
os_release('neutron-common', base='icehouse'))
|
|
return (cmp_release >= 'mitaka' and disable_mlockall)
|
|
|
|
def ovs_ctxt(self):
|
|
# In addition to generating config context, ensure the OVS service
|
|
# is running and the OVS bridge exists. Also need to ensure
|
|
# local_ip points to actual IP, not hostname.
|
|
ovs_ctxt = super(OVSPluginContext, self).ovs_ctxt()
|
|
if not ovs_ctxt:
|
|
return {}
|
|
|
|
conf = config()
|
|
|
|
fallback = get_host_ip(unit_get('private-address'))
|
|
if config('os-data-network'):
|
|
# NOTE: prefer any existing use of config based networking
|
|
ovs_ctxt['local_ip'] = \
|
|
get_address_in_network(config('os-data-network'),
|
|
fallback)
|
|
else:
|
|
# NOTE: test out network-spaces support, then fallback
|
|
try:
|
|
ovs_ctxt['local_ip'] = get_host_ip(
|
|
network_get_primary_address('data')
|
|
)
|
|
except NotImplementedError:
|
|
ovs_ctxt['local_ip'] = fallback
|
|
|
|
neutron_api_settings = NeutronAPIContext()()
|
|
ovs_ctxt['neutron_security_groups'] = self.neutron_security_groups
|
|
ovs_ctxt['l2_population'] = neutron_api_settings['l2_population']
|
|
ovs_ctxt['distributed_routing'] = neutron_api_settings['enable_dvr']
|
|
ovs_ctxt['extension_drivers'] = neutron_api_settings[
|
|
'extension_drivers']
|
|
ovs_ctxt['overlay_network_type'] = \
|
|
neutron_api_settings['overlay_network_type']
|
|
ovs_ctxt['polling_interval'] = neutron_api_settings['polling_interval']
|
|
ovs_ctxt['rpc_response_timeout'] = \
|
|
neutron_api_settings['rpc_response_timeout']
|
|
ovs_ctxt['report_interval'] = neutron_api_settings['report_interval']
|
|
# TODO: We need to sort out the syslog and debug/verbose options as a
|
|
# general context helper
|
|
ovs_ctxt['use_syslog'] = conf['use-syslog']
|
|
ovs_ctxt['verbose'] = conf['verbose']
|
|
ovs_ctxt['debug'] = conf['debug']
|
|
ovs_ctxt['prevent_arp_spoofing'] = conf['prevent-arp-spoofing']
|
|
ovs_ctxt['enable_dpdk'] = conf['enable-dpdk']
|
|
ovs_ctxt['keepalived_healthcheck_interval'] = \
|
|
conf['keepalived-healthcheck-interval']
|
|
ovs_ctxt['disable_mlockall'] = self.disable_mlockall()
|
|
|
|
net_dev_mtu = neutron_api_settings.get('network_device_mtu')
|
|
if net_dev_mtu:
|
|
# neutron.conf
|
|
ovs_ctxt['network_device_mtu'] = net_dev_mtu
|
|
# ml2 conf
|
|
ovs_ctxt['veth_mtu'] = net_dev_mtu
|
|
|
|
mappings = config('bridge-mappings')
|
|
if mappings:
|
|
ovs_ctxt['bridge_mappings'] = ','.join(mappings.split())
|
|
|
|
sriov_mappings = config('sriov-device-mappings')
|
|
if sriov_mappings:
|
|
ovs_ctxt['sriov_device_mappings'] = (
|
|
','.join(sriov_mappings.split())
|
|
)
|
|
|
|
enable_sriov = config('enable-sriov')
|
|
if enable_sriov:
|
|
ovs_ctxt['enable_sriov'] = True
|
|
|
|
sriov_numvfs = config('sriov-numvfs')
|
|
if sriov_numvfs:
|
|
try:
|
|
if sriov_numvfs != 'auto':
|
|
int(sriov_numvfs)
|
|
except ValueError:
|
|
ovs_ctxt['sriov_vfs_list'] = sriov_numvfs
|
|
else:
|
|
ovs_ctxt['sriov_vfs_blanket'] = sriov_numvfs
|
|
|
|
flat_providers = config('flat-network-providers')
|
|
if flat_providers:
|
|
ovs_ctxt['network_providers'] = ','.join(flat_providers.split())
|
|
|
|
vlan_ranges = config('vlan-ranges')
|
|
if vlan_ranges:
|
|
ovs_ctxt['vlan_ranges'] = ','.join(vlan_ranges.split())
|
|
|
|
ovs_ctxt['enable_nsg_logging'] = \
|
|
neutron_api_settings['enable_nsg_logging']
|
|
|
|
ovs_ctxt['nsg_log_output_base'] = get_nsg_log_path(
|
|
config('security-group-log-output-base')
|
|
)
|
|
ovs_ctxt['nsg_log_rate_limit'] = \
|
|
config('security-group-log-rate-limit')
|
|
ovs_ctxt['nsg_log_burst_limit'] = \
|
|
config('security-group-log-burst-limit')
|
|
|
|
ovs_ctxt['firewall_driver'] = _get_firewall_driver(ovs_ctxt)
|
|
|
|
if ovs_ctxt['firewall_driver'] != OPENVSWITCH:
|
|
ovs_ctxt['enable_nsg_logging'] = False
|
|
|
|
ovs_ctxt['of_inactivity_probe'] = config('of-inactivity-probe')
|
|
|
|
return ovs_ctxt
|
|
|
|
|
|
class ZoneContext(OSContextGenerator):
|
|
|
|
def __call__(self):
|
|
"""Return the 'default_availability_zone' from the principal that this
|
|
ovs unit is attached to (as a subordinate)
|
|
|
|
:returns: {} if no relation set, or
|
|
{'availability_zone': availability_zone from principal relation}
|
|
"""
|
|
# as ovs is a subordinate charm, it should only have one relation to
|
|
# its principal charm. Thus we can take the 1st (only) element in each
|
|
# list.
|
|
rids = relation_ids('neutron-plugin')
|
|
ctxt = {}
|
|
if rids:
|
|
rid = rids[0]
|
|
units = related_units(rid)
|
|
if units:
|
|
availability_zone = relation_get(
|
|
'default_availability_zone',
|
|
rid=rid,
|
|
unit=units[0])
|
|
if availability_zone:
|
|
ctxt['availability_zone'] = availability_zone
|
|
return ctxt
|
|
|
|
|
|
class L3AgentContext(OSContextGenerator):
|
|
|
|
def __call__(self):
|
|
neutron_api_settings = NeutronAPIContext()()
|
|
ctxt = {}
|
|
if neutron_api_settings['enable_dvr']:
|
|
use_dvr_snat = config('use-dvr-snat')
|
|
agent_mode = 'dvr_snat' if use_dvr_snat else 'dvr'
|
|
ctxt['agent_mode'] = agent_mode
|
|
ctxt['use_l3ha'] = neutron_api_settings.get('enable_l3ha', False)
|
|
if not config('ext-port'):
|
|
ctxt['external_configuration_new'] = True
|
|
else:
|
|
ctxt['agent_mode'] = 'legacy'
|
|
|
|
ctxt['enable_nfg_logging'] = (
|
|
neutron_api_settings['enable_nfg_logging']
|
|
)
|
|
|
|
ctxt['nfg_log_output_base'] = validate_nfg_log_path(
|
|
config('firewall-group-log-output-base')
|
|
)
|
|
ctxt['nfg_log_rate_limit'] = config(
|
|
'firewall-group-log-rate-limit'
|
|
)
|
|
if ctxt['nfg_log_rate_limit'] is not None:
|
|
ctxt['nfg_log_rate_limit'] = max(
|
|
ctxt['nfg_log_rate_limit'],
|
|
NFG_LOG_RATE_LIMIT_MIN
|
|
)
|
|
ctxt['nfg_log_burst_limit'] = config(
|
|
'firewall-group-log-burst-limit'
|
|
)
|
|
if ctxt['nfg_log_burst_limit'] is not None:
|
|
ctxt['nfg_log_burst_limit'] = max(
|
|
ctxt['nfg_log_burst_limit'],
|
|
NFG_LOG_BURST_LIMIT_MIN
|
|
)
|
|
|
|
l3_extension_plugins = neutron_api_settings.get(
|
|
'l3_extension_plugins', [])
|
|
|
|
ctxt['l3_extension_plugins'] = ','.join(l3_extension_plugins)
|
|
|
|
return ctxt
|
|
|
|
|
|
SHARED_SECRET = "/etc/neutron/secret.txt"
|
|
|
|
|
|
def get_shared_secret():
|
|
secret = None
|
|
if not os.path.exists(SHARED_SECRET):
|
|
secret = str(uuid.uuid4())
|
|
write_file(SHARED_SECRET, secret,
|
|
perms=0o400)
|
|
else:
|
|
os.chmod(SHARED_SECRET, 0o400)
|
|
with open(SHARED_SECRET, 'r') as secret_file:
|
|
secret = secret_file.read().strip()
|
|
return secret
|
|
|
|
|
|
class SharedSecretContext(OSContextGenerator):
|
|
|
|
def __call__(self):
|
|
if NeutronAPIContext()()['enable_dvr'] or \
|
|
config('enable-local-dhcp-and-metadata'):
|
|
ctxt = {
|
|
'shared_secret': get_shared_secret(),
|
|
}
|
|
else:
|
|
ctxt = {}
|
|
return ctxt
|
|
|
|
|
|
class RemoteRestartContext(OSContextGenerator):
|
|
|
|
def __init__(self, interfaces=None):
|
|
self.interfaces = interfaces or ['neutron-plugin']
|
|
|
|
def __call__(self):
|
|
rids = []
|
|
for interface in self.interfaces:
|
|
rids.extend(relation_ids(interface))
|
|
ctxt = {}
|
|
for rid in rids:
|
|
for unit in related_units(rid):
|
|
remote_data = relation_get(
|
|
rid=rid,
|
|
unit=unit)
|
|
for k, v in remote_data.items():
|
|
if k.startswith('restart-trigger'):
|
|
restart_key = k.replace('-', '_')
|
|
try:
|
|
ctxt[restart_key].append(v)
|
|
except KeyError:
|
|
ctxt[restart_key] = [v]
|
|
for restart_key in ctxt.keys():
|
|
ctxt[restart_key] = '-'.join(sorted(ctxt[restart_key]))
|
|
return ctxt
|
|
|
|
|
|
class APIIdentityServiceContext(context.IdentityServiceContext):
|
|
|
|
def __init__(self):
|
|
super(APIIdentityServiceContext,
|
|
self).__init__(rel_name='neutron-plugin-api')
|
|
|
|
def __call__(self):
|
|
ctxt = super(APIIdentityServiceContext, self).__call__()
|
|
if not ctxt:
|
|
return
|
|
for rid in relation_ids('neutron-plugin-api'):
|
|
for unit in related_units(rid):
|
|
rdata = relation_get(rid=rid, unit=unit)
|
|
ctxt['region'] = rdata.get('region')
|
|
if ctxt['region']:
|
|
return ctxt
|
|
return ctxt
|