charm-octavia/src/lib/charm/openstack/api_crud.py

885 lines
36 KiB
Python

# Copyright 2018 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.
# NOTE(fnordahl) imported dependencies are included in the reactive charm
# ``wheelhouse.txt`` and are isolated from any system installed payload managed
# by the charm.
#
# An alternative could be to execute the openstack CLI to manage the resources,
# but at the time of this writing we can not due to it producing invalid JSON
# and YAML for the ``fixed_ips`` field when providing details for a Neutron
# port.
import base64
import neutronclient
import socket
import subprocess
import tenacity
from keystoneauth1 import identity as keystone_identity
from keystoneauth1 import session as keystone_session
from keystoneauth1 import exceptions as keystone_exceptions
from neutronclient.v2_0 import client as neutron_client
from novaclient import client as nova_client
import neutron_lib.constants
import charm.openstack.octavia as octavia # for constants
import charmhelpers.core as ch_core
NEUTRON_TEMP_EXCS = (keystone_exceptions.catalog.EndpointNotFound,
keystone_exceptions.connection.ConnectFailure,
keystone_exceptions.discovery.DiscoveryFailure,
keystone_exceptions.http.ServiceUnavailable,
keystone_exceptions.http.InternalServerError,
neutronclient.common.exceptions.ServiceUnavailable,
neutronclient.common.exceptions.BadRequest,
neutronclient.common.exceptions.NeutronClientException)
SYSTEM_CA_BUNDLE = '/etc/ssl/certs/ca-certificates.crt'
class APIUnavailable(Exception):
"""Exception raised when a temporary availability issue occurs."""
def __init__(self, service_type, resource_type, upstream_exception):
"""Initialize APIUnavailable exception.
:param service_type: Name of service we had issues with (e.g. `nova`).
:type service_type: str
:param resource_type: Name of resource we had issues with
(e.g. `flavors`)
:type resource_type: str
:param upstream_exception: Reference to the exception caught
:type upstream_exception: BaseException derived object
"""
self.service_type = service_type
self.resource_type = resource_type
self.upstream_exception = upstream_exception
class DuplicateResource(Exception):
"""Exception raised when resource query result in multiple entries."""
def __init__(self, service_type, resource_type, data=None):
"""Initialize DuplicateResource exception.
:param service_type: Name of service we had issues with (e.g. `nova`).
:type service_type: str
:param resource_type: Name of resource we had issues with
(e.g. `flavors`)
:type resource_type: str
:param data: Data from search result
:type data: (Optional)any
"""
self.service_type = service_type
self.resource_type = resource_type
self.data = data
def endpoint_type():
"""Determine endpoint type to use.
:returns: endpoint type
:rtype: str
"""
if ch_core.hookenv.config('use-internal-endpoints'):
return 'internalURL'
return 'publicURL'
def session_from_identity_service(identity_service):
"""Get Keystone Session from `identity-service` relation.
:param identity_service: reactive Endpoint
:type identity_service: RelationBase
:returns: Keystone session
:rtype: keystone_session.Session
"""
auth = keystone_identity.Password(
auth_url='{}://{}:{}/'
.format(identity_service.auth_protocol(),
identity_service.auth_host(),
identity_service.auth_port()),
user_domain_name=identity_service.service_domain(),
username=identity_service.service_username(),
password=identity_service.service_password(),
project_domain_name=identity_service.service_domain(),
project_name=identity_service.service_tenant(),
)
# NOTE(fnordahl): LP: #1819205 since the charm bundles its dependencies we
# do not get the patched python ``certifi`` package that ponits at the
# system wide certificate store. We need to point clients there ourself.
return keystone_session.Session(auth=auth, verify=SYSTEM_CA_BUNDLE)
def init_neutron_client(keystone_session):
"""Instantiate neutron client
:param keystone_session: Keystone client auth session
:type keystone_session.Session
:returns: Neutron client
:rtype: neutron_client.Client
"""
return neutron_client.Client(session=keystone_session,
region_name=ch_core.hookenv.config('region'),
endpoint_type=endpoint_type(),
)
def get_nova_client(keystone_session):
"""Get Nova client
:param keystone_session: Keystone client auth session
:type keystone_session.Session
:returns: Nova client
:rtype: nova_client.Client
"""
return nova_client.Client('2',
session=keystone_session,
region_name=ch_core.hookenv.config('region'),
endpoint_type=endpoint_type(),
)
def is_extension_enabled(neutron_client, ext_alias):
"""Check for presence of Neutron extension
:param neutron_client:
:type neutron_client:
:returns: True if Neutron lists extension, False otherwise
:rtype: bool
"""
for extension in neutron_client.list_extensions().get('extensions', []):
if extension.get('alias') == ext_alias:
return True
return False
def get_nova_flavor(identity_service):
"""Get or create private Nova flavor for use with Octavia.
A side effect of calling this function is that Nova flavors are
created if they do not already exist.
:param identity_service: reactive Endpoint of type ``identity-service``
:type identity_service: RelationBase class
:returns: Nova Flavor Resource object
:rtype: novaclient.v2.flavors.Flavor
"""
try:
session = session_from_identity_service(identity_service)
nova = get_nova_client(session)
flavors = nova.flavors.list(is_public=False)
for flavor in flavors:
if flavor.name == 'charm-octavia':
return flavor
# create flavor
return nova.flavors.create('charm-octavia', 1024, 1, 8,
is_public=False)
except (keystone_exceptions.catalog.EndpointNotFound,
keystone_exceptions.connection.ConnectFailure,
nova_client.exceptions.ClientException) as e:
raise APIUnavailable('nova', 'flavors', e)
def create_nova_keypair(identity_service, amp_key_name):
"""Create a nova keypair to use with Amphora images to allow ssh access
e.g. for debug purposes.
"""
pubkey = ch_core.hookenv.config('amp-ssh-pub-key')
if not pubkey:
ch_core.hookenv.log('No pub key provided - cannot create amp-ssh-key '
'keypair', level=ch_core.hookenv.WARNING)
return
pubkey_decoded = base64.b64decode(pubkey).strip().decode()
try:
session = session_from_identity_service(identity_service)
nova = get_nova_client(session)
keys = nova.keypairs.list()
for key in keys:
if key.name == amp_key_name:
ch_core.hookenv.log("Nova keypair with name '{}' already "
"exists - skipping create"
.format(amp_key_name),
level=ch_core.hookenv.INFO)
return
# create keypair
ch_core.hookenv.log("Creating nova keypair '{}'".format(amp_key_name),
level=ch_core.hookenv.DEBUG)
return nova.keypairs.create(name=amp_key_name,
public_key=pubkey_decoded)
except (keystone_exceptions.catalog.EndpointNotFound,
keystone_exceptions.connection.ConnectFailure,
nova_client.exceptions.ClientException) as e:
raise APIUnavailable('nova', 'keypairs', e)
def lookup_hm_port(nc, local_unit_name):
"""Retrieve port object for Neutron port for local unit.
:param nc: Neutron Client object
:type nc: neutron_client.Client
:param local_unit_name: Name of juju unit, used to build tag name for port
:type local_unit_name: str
:returns: Port data
:rtype: Optional[Dict[str,any]]
:raises: DuplicateResource or any exceptions raised by Keystone and Neutron
clients.
"""
resp = nc.list_ports(tags='charm-octavia-{}'.format(local_unit_name))
n_resp = len(resp.get('ports', []))
if n_resp == 1:
return (resp['ports'][0])
elif n_resp > 1:
raise DuplicateResource('neutron', 'ports', data=resp)
else:
return
def get_hm_port(identity_service, local_unit_name, local_unit_address,
host_id=None):
"""Get or create a per unit Neutron port for Octavia Health Manager.
A side effect of calling this function is that a port is created if one
does not already exist.
:param identity_service: reactive Endpoint of type ``identity-service``
:type identity_service: RelationBase class
:param local_unit_name: Name of juju unit, used to build tag name for port
:type local_unit_name: str
:param local_unit_address: DNS resolvable IP address of unit, used to
build Neutron port ``binding:host_id``
:type local_unit_address: str
:param host_id: Identifier used by SDN for binding the port
:type host_id: Option[None,str]
:returns: Port details extracted from result of call to
neutron_client.list_ports or neutron_client.create_port
:rtype: dict
:raises: api_crud.APIUnavailable, api_crud.DuplicateResource
"""
session = session_from_identity_service(identity_service)
try:
nc = init_neutron_client(session)
resp = nc.list_networks(tags='charm-octavia')
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'networks', e)
network = None
n_resp = len(resp.get('networks', []))
if n_resp == 1:
network = resp['networks'][0]
elif n_resp > 1:
raise DuplicateResource('neutron', 'networks', data=resp)
else:
ch_core.hookenv.log('No network tagged with `charm-octavia` exists, '
'deferring port setup awaiting network and port '
'(re-)creation.', level=ch_core.hookenv.WARNING)
return
health_secgrp = None
try:
resp = nc.list_security_groups(tags='charm-octavia-health')
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'security_groups', e)
n_resp = len(resp.get('security_groups', []))
if n_resp == 1:
health_secgrp = resp['security_groups'][0]
elif n_resp > 1:
raise DuplicateResource('neutron', 'security_groups', data=resp)
else:
ch_core.hookenv.log('No security group tagged with '
'`charm-octavia-health` exists, deferring '
'port setup awaiting network and port '
'(re-)creation...',
level=ch_core.hookenv.WARNING)
return
try:
hm_port = lookup_hm_port(nc, local_unit_name)
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'ports', e)
port_template = {
'port': {
# avoid race with OVS agent attempting to bind port
# before it is created in the local units OVSDB
'admin_state_up': False,
'binding:host_id': host_id or socket.gethostname(),
# NOTE(fnordahl): device_owner has special meaning
# for Neutron [0], and things may break if set to
# an arbritary value. Using a value known by Neutron
# is_dvr_serviced() function [1] gets us the correct
# rules appiled to the port to allow IPv6 Router
# Advertisement packets through LP: #1813931
# 0: https://github.com/openstack/neutron/blob/
# 916347b996684c82b29570cd2962df3ea57d4b16/
# neutron/plugins/ml2/drivers/openvswitch/
# agent/ovs_dvr_neutron_agent.py#L592
# 1: https://github.com/openstack/neutron/blob/
# 50308c03c960bd6e566f328a790b8e05f5e92ead/
# neutron/common/utils.py#L200
'device_owner': (
neutron_lib.constants.DEVICE_OWNER_LOADBALANCERV2),
'security_groups': [
health_secgrp['id'],
],
'name': 'octavia-health-manager-{}-listen-port'
.format(local_unit_name),
'network_id': network['id'],
},
}
if not hm_port:
# create new port
try:
resp = nc.create_port(port_template)
hm_port = resp['port']
ch_core.hookenv.log('Created port {}'.format(hm_port['id']),
ch_core.hookenv.INFO)
# unit specific tag is used by each unit to load their state
nc.add_tag('ports', hm_port['id'],
'charm-octavia-{}'
.format(local_unit_name))
# charm-wide tag is used by leader to load cluster state and build
# ``controller_ip_port_list`` configuration property
nc.add_tag('ports', hm_port['id'], 'charm-octavia')
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'ports', e)
elif hm_port.get(
'binding:host_id') != port_template['port']['binding:host_id']:
# Ensure binding:host_id is up to date on a existing port
#
# In the event of a need to update it, we bring the port down to make
# sure Neutron rebuilds the port correctly.
#
# Our caller, ``setup_hm_port``, will toggle the port admin status.
try:
nc.update_port(hm_port['id'], {
'port': {
'admin_state_up': False,
'binding:host_id': port_template['port'][
'binding:host_id'],
}
})
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'ports', e)
return hm_port
def toggle_hm_port(identity_service, local_unit_name, enabled=True):
"""Toggle administrative state of Neutron port for local unit.
:param identity_service: reactive Endpoint of type ``identity-service``
:type identity_service: RelationBase class
:param local_unit_name: Name of juju unit, used to build tag name for port
:type local_unit_name: str
:param enabled: Desired state
:type enabled: bool
:raises: api_crud.APIUnavailable
"""
session = session_from_identity_service(identity_service)
try:
nc = init_neutron_client(session)
port = lookup_hm_port(nc, local_unit_name)
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'ports', e)
if not port:
ch_core.hookenv.log('When attempting to toggle admin status of port, '
'we unexpectedly found that no port exists for '
'unit.',
level=ch_core.hookenv.WARNING)
return
nc.update_port(port['id'], {'port': {'admin_state_up': enabled}})
def is_hm_port_bound(identity_service, local_unit_name):
"""Retrieve bound status of Neutron port for local unit.
:param identity_service: reactive Endpoint of type ``identity-service``
:type identity_service: RelationBase class
:param local_unit_name: Name of juju unit, used to build tag name for port
:type local_unit_name: str
:returns: True if up, False if down, None if no port found
:rtype: Optional[bool]
:raises: api_crud.APIUnavailable
"""
session = session_from_identity_service(identity_service)
try:
nc = init_neutron_client(session)
port = lookup_hm_port(nc, local_unit_name)
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'ports', e)
if port:
# The operational status field for our port is unfortunately not
# accurate. But we can use the binding_vif_type field to detect if
# binding failed.
return port['binding:vif_type'] != 'binding_failed'
def wait_for_hm_port_bound(identity_service, local_unit_name):
"""Wait for port binding status of Neutron port for local unit.
:param identity_service: reactive Endpoint of type ``identity-service``
:type identity_service: RelationBase class
:param local_unit_name: Name of juju unit, used to build tag name for port
:type local_unit_name: str
:returns: True if state was reached, False otherwise
"""
try:
for attempt in tenacity.Retrying(
stop=tenacity.stop_after_attempt(3),
wait=tenacity.wait_exponential(
multiplier=1, min=2, max=10)):
with attempt:
port_up = is_hm_port_bound(
identity_service, local_unit_name)
assert port_up is True
return port_up
except tenacity.RetryError:
return False
def ensure_hm_port_mtu(identity_service):
"""
Ensure the Octavia health manager port has the same mtu as the network it
is attached to. This is to ensure that of the mtu changes in Neutron it is
reflected here as well.
"""
session = session_from_identity_service(identity_service)
nc = init_neutron_client(session)
resp = nc.list_networks(tags='charm-octavia')
if len(resp['networks']) > 0:
network = resp['networks'][0]
ch_core.hookenv.log('ensuring mgmt network {} mtu={}'.
format(network['id'], network['mtu']),
level=ch_core.hookenv.DEBUG)
try:
subprocess.check_call(
['ovs-vsctl', 'set', 'Interface', octavia.OCTAVIA_MGMT_INTF,
'mtu={}'.format(network['mtu'])])
subprocess.check_call(
['ip', 'link', 'set', octavia.OCTAVIA_MGMT_INTF, 'mtu',
str(network['mtu'])])
except subprocess.CalledProcessError as exc:
ch_core.hookenv.log("failed to apply mtu to interface '{}': {}".
format(octavia.OCTAVIA_MGMT_INTF, exc),
level=ch_core.hookenv.DEBUG)
else:
ch_core.hookenv.log('mgmt network not found - cannot set mtu')
def setup_hm_port(identity_service, octavia_charm, host_id=None):
"""Create a per unit Neutron and OVS port for Octavia Health Manager.
This is used to plug the unit into the overlay network for direct
communication with the octavia managed load balancer instances running
within the deployed cloud.
:param identity_service: reactive Endpoint of type ``identity-service``
:type identity_service: RelationBase class
:param ocataiva_charm: charm instance
:type octavia_charm: OctaviaCharm class instance
:param host_id: Identifier used by SDN for binding the port
:type host_id: Option[None,str]
:retruns: True on change to local unit, False otherwise
:rtype: bool
:raises: api_crud.APIUnavailable, api_crud.DuplicateResource
"""
unit_changed = False
hm_port = get_hm_port(
identity_service,
octavia_charm.local_unit_name,
octavia_charm.local_address,
host_id=host_id)
if not hm_port:
ch_core.hookenv.log('No network tagged with `charm-octavia` '
'exists, deferring port setup awaiting '
'network and port (re-)creation...',
level=ch_core.hookenv.WARNING)
return
HM_PORT_MAC = hm_port['mac_address']
HM_PORT_ID = hm_port['id']
try:
# TODO: We should add/update the OVS port and interface parameters on
# every call. Otherwise mac-address binding may get out of sync with
# what is in Neutron on port re-create etc. Remove the pre-flight ip
# link check and call ovs-vsctl with ``--may-exist`` instead. We would
# need to add some code to check if something changed or not.
subprocess.check_output(
['ip', 'link', 'show', octavia.OCTAVIA_MGMT_INTF],
stderr=subprocess.STDOUT, universal_newlines=True)
except subprocess.CalledProcessError as e:
if 'does not exist' in e.output:
subprocess.check_call(
['ovs-vsctl', '--', 'add-port',
octavia.OCTAVIA_INT_BRIDGE, octavia.OCTAVIA_MGMT_INTF,
'--', 'set', 'Interface', octavia.OCTAVIA_MGMT_INTF,
'type=internal',
'--', 'set', 'Interface', octavia.OCTAVIA_MGMT_INTF,
'external-ids:iface-status=active',
'--', 'set', 'Interface', octavia.OCTAVIA_MGMT_INTF,
'external-ids:attached-mac={}'.format(HM_PORT_MAC),
'--', 'set', 'Interface', octavia.OCTAVIA_MGMT_INTF,
'external-ids:iface-id={}'.format(HM_PORT_ID),
'--', 'set', 'Interface', octavia.OCTAVIA_MGMT_INTF,
'external-ids:skip_cleanup=true',
])
ch_core.hookenv.log('add OVS port', level=ch_core.hookenv.INFO)
# post boot reconfiguration of systemd-networkd does not appear to
# set the MAC addresss on the interface, do it ourself.
subprocess.check_call(
['ip', 'link', 'set', octavia.OCTAVIA_MGMT_INTF,
'up', 'address', HM_PORT_MAC])
# Signal that change has been made to local unit
unit_changed = True
else:
# unknown error, raise
raise e
# NOTE: apply this always to ensure consistency
ensure_hm_port_mtu(identity_service)
if (not hm_port['admin_state_up'] or
not is_hm_port_bound(identity_service,
octavia_charm.local_unit_name) or
hm_port['status'] == 'DOWN'):
# NOTE(fnordahl) there appears to be a handful of race conditions
# hitting us sometimes making the newly created ports unusable.
# as a workaround we toggle the port belonging to us.
# a disable/enable round trip makes Neutron reset the port
# configuration which resolves these situations.
ch_core.hookenv.log('toggling port {} (admin_state_up: {} '
'status: {} binding:vif_type: {})'
.format(hm_port['id'],
hm_port['admin_state_up'],
hm_port['status'],
hm_port['binding:vif_type']),
level=ch_core.hookenv.INFO)
toggle_hm_port(identity_service,
octavia_charm.local_unit_name,
enabled=False)
toggle_hm_port(identity_service,
octavia_charm.local_unit_name,
enabled=True)
return unit_changed
def get_port_ips(identity_service):
"""Extract IP information from Neutron ports tagged with ``charm-octavia``
:param identity_service: reactive Endpoint of type ``identity-service``
:type identity_service: RelationBase class
:returns: List of IP addresses extracted from port details in search result
:rtype: list of str
:raises: api_crud.APIUnavailable
"""
session = session_from_identity_service(identity_service)
try:
nc = init_neutron_client(session)
resp = nc.list_ports(tags='charm-octavia')
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'ports', e)
neutron_ip_list = []
for port in resp['ports']:
for ip_info in port['fixed_ips']:
neutron_ip_list.append(ip_info['ip_address'])
return neutron_ip_list
def get_mgmt_network(identity_service, create=True):
"""Get or create Neutron network resources for Octavia.
A side effect of calling this function is that network resources are
created if they do not already exist, unless ``create`` is set to False.
:param identity_service: reactive Endpoint of type ``identity-service``
:type identity_service: RelationBase class
:param create: (Optional and default) Create resources that do not exist
:type: create: bool
:returns: List of IP addresses extracted from port details in search result
:rtype: list of str
:raises: api_crud.APIUnavailable, api_crud.DuplicateResource
"""
session = session_from_identity_service(identity_service)
try:
nc = init_neutron_client(session)
resp = nc.list_networks(tags='charm-octavia')
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'networks', e)
n_resp = len(resp.get('networks', []))
if n_resp == 1:
network = resp['networks'][0]
elif n_resp > 1:
raise DuplicateResource('neutron', 'networks', data=resp)
elif not create:
ch_core.hookenv.log('No network tagged with `charm-octavia` exists, '
'and we are configured to not create resources.'
'Awaiting end user resource creation.',
level=ch_core.hookenv.WARNING)
return
else:
try:
resp = nc.create_network({
'network': {'name': octavia.OCTAVIA_MGMT_NET}})
network = resp['network']
nc.add_tag('networks', network['id'], 'charm-octavia')
ch_core.hookenv.log('Created network {}'.format(network['id']),
level=ch_core.hookenv.INFO)
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'networks', e)
try:
# The service user can see other subnets that are tagged with
# 'charm-octavia' but are not part of this service's lb-mgmt-net. To
# avoid that, ensure that the subnets are filtered by the network the
# charm cares about.
resp = nc.list_subnets(network_id=network['id'], tags='charm-octavia')
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'subnets', e)
subnets = resp.get('subnets', [])
n_resp = len(subnets)
if n_resp < 1 and create:
# make rfc4193 Unique Local IPv6 Unicast Addresses from network UUID
rfc4193_addr = 'fc00'
for n in [0, 4, 8]:
rfc4193_addr += ':' + network['id'].split('-')[4][n:n + 4]
rfc4193_addr += '::/64'
try:
resp = nc.create_subnet(
{
'subnets': [
{
'name': octavia.OCTAVIA_MGMT_SUBNET + 'v6',
'ip_version': 6,
'ipv6_address_mode': 'slaac',
'ipv6_ra_mode': 'slaac',
'cidr': rfc4193_addr,
'network_id': network['id'],
},
],
})
subnets = resp['subnets']
for subnet in resp['subnets']:
nc.add_tag('subnets', subnet['id'], 'charm-octavia')
ch_core.hookenv.log('Created subnet {} with cidr {}'
.format(subnet['id'], subnet['cidr']),
level=ch_core.hookenv.INFO)
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'subnets', e)
try:
resp = nc.list_routers(tags='charm-octavia')
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'routers', e)
n_resp = len(resp.get('routers', []))
router = None
if n_resp < 1 and create:
try:
body = {
'router': {
'name': octavia.OCTAVIA_MGMT_NAME_PREFIX,
},
}
# NOTE(fnordahl): Using the ``distributed`` key in a request to
# Neutron is an error when the DVR extension is not enabled.
if is_extension_enabled(nc, 'dvr'):
# NOTE(fnordahl): When DVR is enabled we want to use a
# centralized router to support assigning addresses with IPv6
# RA. LP: #1843557
body['router'].update({'distributed': False})
resp = nc.create_router(body)
router = resp['router']
nc.add_tag('routers', router['id'], 'charm-octavia')
ch_core.hookenv.log('Created router {}'.format(router['id']),
level=ch_core.hookenv.INFO)
for subnet in subnets:
nc.add_interface_router(router['id'],
{'subnet_id': subnet['id']})
ch_core.hookenv.log('Added interface from router {} '
'to subnet {}'
.format(router['id'], subnet['id']),
level=ch_core.hookenv.INFO)
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'routers', e)
try:
resp = nc.list_security_groups(tags='charm-octavia')
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'security_groups', e)
n_resp = len(resp.get('security_groups', []))
if n_resp == 1:
secgrp = resp['security_groups'][0]
elif n_resp > 1:
raise DuplicateResource('neutron', 'security_groups', data=resp)
elif not create:
ch_core.hookenv.log('No security group tagged with `charm-octavia` '
'exists, and we are configured to not create '
'resources. Awaiting end user resource '
'creation.',
level=ch_core.hookenv.WARNING)
return
else:
try:
resp = nc.create_security_group(
{
'security_group': {
'name': octavia.OCTAVIA_MGMT_SECGRP,
},
})
secgrp = resp['security_group']
nc.add_tag('security_groups', secgrp['id'], 'charm-octavia')
ch_core.hookenv.log('Created security group "{}"'
.format(secgrp['id']),
level=ch_core.hookenv.INFO)
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'security_groups', e)
if create:
security_group_rules = [
{
'direction': 'ingress',
'protocol': 'icmpv6',
'ethertype': 'IPv6',
'security_group_id': secgrp['id'],
},
{
'direction': 'ingress',
'protocol': 'tcp',
'ethertype': 'IPv6',
'port_range_min': '22',
'port_range_max': '22',
'security_group_id': secgrp['id'],
},
{
'direction': 'ingress',
'protocol': 'tcp',
'ethertype': 'IPv6',
'port_range_min': '9443',
'port_range_max': '9443',
'security_group_id': secgrp['id'],
},
]
for rule in security_group_rules:
try:
nc.create_security_group_rule({'security_group_rule': rule})
except neutronclient.common.exceptions.Conflict:
pass
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'security_group_rules', e)
try:
resp = nc.list_security_groups(tags='charm-octavia-health')
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'security_groups', e)
n_resp = len(resp.get('security_groups', []))
if n_resp == 1:
health_secgrp = resp['security_groups'][0]
elif n_resp > 1:
raise DuplicateResource('neutron', 'security_groups', data=resp)
elif not create:
ch_core.hookenv.log('No security group tagged with '
'`charm-octavia-health` exists, and we are '
'configured to not create resources. Awaiting '
'end user resource creation.',
level=ch_core.hookenv.WARNING)
return
else:
try:
resp = nc.create_security_group(
{
'security_group': {
'name': octavia.OCTAVIA_HEALTH_SECGRP,
},
})
health_secgrp = resp['security_group']
nc.add_tag('security_groups', health_secgrp['id'],
'charm-octavia-health')
ch_core.hookenv.log('Created security group "{}"'
.format(health_secgrp['id']),
level=ch_core.hookenv.INFO)
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'security_groups', e)
if create:
health_security_group_rules = [
{
'direction': 'ingress',
'protocol': 'icmpv6',
'ethertype': 'IPv6',
'security_group_id': health_secgrp['id'],
},
{
'direction': 'ingress',
'protocol': 'udp',
'ethertype': 'IPv6',
'port_range_min': octavia.OCTAVIA_HEALTH_LISTEN_PORT,
'port_range_max': octavia.OCTAVIA_HEALTH_LISTEN_PORT,
'security_group_id': health_secgrp['id'],
},
]
for rule in health_security_group_rules:
try:
nc.create_security_group_rule({'security_group_rule': rule})
except neutronclient.common.exceptions.Conflict:
pass
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'security_groups', e)
resp = nc.list_security_group_rules(security_group_id=health_secgrp['id'])
return (network, secgrp)
def set_service_quotas_unlimited(identity_service):
"""Set services project quotas to unlimited.
:param identity_service: reactive Endpoint of type ``identity-service``
:type identity_service: RelationBase class
:returns: None
:rtype: None
:raises: api_crud.APIUnavailable
"""
try:
_ul = -1
session = session_from_identity_service(identity_service)
nova = get_nova_client(session)
nova.quotas.update(
identity_service.service_tenant_id(),
cores=_ul, ram=_ul, instances=_ul)
nc = init_neutron_client(session)
nc.update_quota(
identity_service.service_tenant_id(),
body={
"quota": {
"port": _ul, "security_group": _ul,
"security_group_rule": _ul, "network": _ul, "subnet": _ul,
"floatingip": _ul, "router": _ul, "rbac_policy": _ul}})
except (keystone_exceptions.catalog.EndpointNotFound,
keystone_exceptions.connection.ConnectFailure,
nova_client.exceptions.ClientException) as e:
raise APIUnavailable('nova', 'quotas', e)
except NEUTRON_TEMP_EXCS as e:
raise APIUnavailable('neutron', 'quotas', e)