diff options
Diffstat (limited to 'aria/multivim-plugin/neutron_plugin')
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/__init__.py | 1 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/floatingip.py | 104 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/network.py | 109 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/port.py | 222 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/router.py | 215 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/security_group.py | 130 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/subnet.py | 101 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/tests/__init__.py | 1 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/tests/test.py | 220 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/tests/test_port.py | 156 | ||||
-rw-r--r-- | aria/multivim-plugin/neutron_plugin/tests/test_security_group.py | 115 |
11 files changed, 0 insertions, 1374 deletions
diff --git a/aria/multivim-plugin/neutron_plugin/__init__.py b/aria/multivim-plugin/neutron_plugin/__init__.py deleted file mode 100644 index 04cb21f745..0000000000 --- a/aria/multivim-plugin/neutron_plugin/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'idanmo' diff --git a/aria/multivim-plugin/neutron_plugin/floatingip.py b/aria/multivim-plugin/neutron_plugin/floatingip.py deleted file mode 100644 index 1a9d0449ca..0000000000 --- a/aria/multivim-plugin/neutron_plugin/floatingip.py +++ /dev/null @@ -1,104 +0,0 @@ -######### -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# 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. - -from cloudify import ctx -from cloudify.decorators import operation -from cloudify.exceptions import NonRecoverableError -from openstack_plugin_common import ( - with_neutron_client, - provider, - is_external_relationship, - is_external_relationship_not_conditionally_created, - OPENSTACK_ID_PROPERTY -) -from openstack_plugin_common.floatingip import ( - use_external_floatingip, - set_floatingip_runtime_properties, - delete_floatingip, - floatingip_creation_validation -) - - -@operation -@with_neutron_client -def create(neutron_client, args, **kwargs): - - if use_external_floatingip(neutron_client, 'floating_ip_address', - lambda ext_fip: ext_fip['floating_ip_address']): - return - - floatingip = { - # No defaults - } - floatingip.update(ctx.node.properties['floatingip'], **args) - - # Sugar: floating_network_name -> (resolve) -> floating_network_id - if 'floating_network_name' in floatingip: - floatingip['floating_network_id'] = neutron_client.cosmo_get_named( - 'network', floatingip['floating_network_name'])['id'] - del floatingip['floating_network_name'] - elif 'floating_network_id' not in floatingip: - provider_context = provider(ctx) - ext_network = provider_context.ext_network - if ext_network: - floatingip['floating_network_id'] = ext_network['id'] - else: - raise NonRecoverableError( - 'Missing floating network id, name or external network') - - fip = neutron_client.create_floatingip( - {'floatingip': floatingip})['floatingip'] - set_floatingip_runtime_properties(fip['id'], fip['floating_ip_address']) - - ctx.logger.info('Floating IP creation response: {0}'.format(fip)) - - -@operation -@with_neutron_client -def delete(neutron_client, **kwargs): - delete_floatingip(neutron_client) - - -@operation -@with_neutron_client -def creation_validation(neutron_client, **kwargs): - floatingip_creation_validation(neutron_client, 'floating_ip_address') - - -@operation -@with_neutron_client -def connect_port(neutron_client, **kwargs): - if is_external_relationship_not_conditionally_created(ctx): - return - - port_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY] - floating_ip_id = ctx.target.instance.runtime_properties[ - OPENSTACK_ID_PROPERTY] - fip = {'port_id': port_id} - neutron_client.update_floatingip(floating_ip_id, {'floatingip': fip}) - - -@operation -@with_neutron_client -def disconnect_port(neutron_client, **kwargs): - if is_external_relationship(ctx): - ctx.logger.info('Not disassociating floatingip and port since ' - 'external floatingip and port are being used') - return - - floating_ip_id = ctx.target.instance.runtime_properties[ - OPENSTACK_ID_PROPERTY] - fip = {'port_id': None} - neutron_client.update_floatingip(floating_ip_id, {'floatingip': fip}) diff --git a/aria/multivim-plugin/neutron_plugin/network.py b/aria/multivim-plugin/neutron_plugin/network.py deleted file mode 100644 index eadcc3b4e8..0000000000 --- a/aria/multivim-plugin/neutron_plugin/network.py +++ /dev/null @@ -1,109 +0,0 @@ -######### -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# 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. - -from cloudify import ctx -from cloudify.decorators import operation -from cloudify.exceptions import NonRecoverableError -from openstack_plugin_common import ( - transform_resource_name, - with_neutron_client, - get_resource_id, - is_external_resource, - is_external_resource_not_conditionally_created, - delete_resource_and_runtime_properties, - use_external_resource, - validate_resource, - OPENSTACK_ID_PROPERTY, - OPENSTACK_TYPE_PROPERTY, - OPENSTACK_NAME_PROPERTY, - COMMON_RUNTIME_PROPERTIES_KEYS -) - -NETWORK_OPENSTACK_TYPE = 'network' - -# Runtime properties -RUNTIME_PROPERTIES_KEYS = COMMON_RUNTIME_PROPERTIES_KEYS - - -@operation -@with_neutron_client -def create(neutron_client, args, **kwargs): - - if use_external_resource(ctx, neutron_client, NETWORK_OPENSTACK_TYPE): - return - - network = { - 'admin_state_up': True, - 'name': get_resource_id(ctx, NETWORK_OPENSTACK_TYPE), - } - network.update(ctx.node.properties['network'], **args) - transform_resource_name(ctx, network) - - net = neutron_client.create_network({'network': network})['network'] - ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY] = net['id'] - ctx.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY] =\ - NETWORK_OPENSTACK_TYPE - ctx.instance.runtime_properties[OPENSTACK_NAME_PROPERTY] = net['name'] - - -@operation -@with_neutron_client -def start(neutron_client, **kwargs): - network_id = ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY] - - if is_external_resource_not_conditionally_created(ctx): - ctx.logger.info('Validating external network is started') - if not neutron_client.show_network( - network_id)['network']['admin_state_up']: - raise NonRecoverableError( - 'Expected external resource network {0} to be in ' - '"admin_state_up"=True'.format(network_id)) - return - - neutron_client.update_network( - network_id, { - 'network': { - 'admin_state_up': True - } - }) - - -@operation -@with_neutron_client -def stop(neutron_client, **kwargs): - if is_external_resource(ctx): - ctx.logger.info('Not stopping network since an external network is ' - 'being used') - return - - neutron_client.update_network( - ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY], { - 'network': { - 'admin_state_up': False - } - }) - - -@operation -@with_neutron_client -def delete(neutron_client, **kwargs): - delete_resource_and_runtime_properties(ctx, neutron_client, - RUNTIME_PROPERTIES_KEYS) - - -@operation -@with_neutron_client -def creation_validation(neutron_client, **kwargs): - validate_resource(ctx, neutron_client, NETWORK_OPENSTACK_TYPE) diff --git a/aria/multivim-plugin/neutron_plugin/port.py b/aria/multivim-plugin/neutron_plugin/port.py deleted file mode 100644 index 4db4c442c5..0000000000 --- a/aria/multivim-plugin/neutron_plugin/port.py +++ /dev/null @@ -1,222 +0,0 @@ -######### -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# 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. - -from cloudify import ctx -from cloudify.decorators import operation -from cloudify.exceptions import NonRecoverableError - -import neutronclient.common.exceptions as neutron_exceptions - -from openstack_plugin_common import ( - transform_resource_name, - with_neutron_client, - with_nova_client, - get_resource_id, - get_openstack_id_of_single_connected_node_by_openstack_type, - delete_resource_and_runtime_properties, - delete_runtime_properties, - use_external_resource, - is_external_relationship, - validate_resource, - OPENSTACK_ID_PROPERTY, - OPENSTACK_TYPE_PROPERTY, - OPENSTACK_NAME_PROPERTY, - COMMON_RUNTIME_PROPERTIES_KEYS, - is_external_relationship_not_conditionally_created) - -from neutron_plugin.network import NETWORK_OPENSTACK_TYPE -from neutron_plugin.subnet import SUBNET_OPENSTACK_TYPE -from openstack_plugin_common.floatingip import get_server_floating_ip - -PORT_OPENSTACK_TYPE = 'port' - -# Runtime properties -FIXED_IP_ADDRESS_PROPERTY = 'fixed_ip_address' # the fixed ip address -MAC_ADDRESS_PROPERTY = 'mac_address' # the mac address -RUNTIME_PROPERTIES_KEYS = \ - COMMON_RUNTIME_PROPERTIES_KEYS + [FIXED_IP_ADDRESS_PROPERTY, - MAC_ADDRESS_PROPERTY] - -NO_SG_PORT_CONNECTION_RETRY_INTERVAL = 3 - - -@operation -@with_neutron_client -def create(neutron_client, args, **kwargs): - - ext_port = use_external_resource(ctx, neutron_client, PORT_OPENSTACK_TYPE) - if ext_port: - try: - net_id = \ - get_openstack_id_of_single_connected_node_by_openstack_type( - ctx, NETWORK_OPENSTACK_TYPE, True) - - if net_id: - port_id = ctx.instance.runtime_properties[ - OPENSTACK_ID_PROPERTY] - - if neutron_client.show_port( - port_id)['port']['network_id'] != net_id: - raise NonRecoverableError( - 'Expected external resources port {0} and network {1} ' - 'to be connected'.format(port_id, net_id)) - - ctx.instance.runtime_properties[FIXED_IP_ADDRESS_PROPERTY] = \ - _get_fixed_ip(ext_port) - ctx.instance.runtime_properties[MAC_ADDRESS_PROPERTY] = \ - ext_port['mac_address'] - return - except Exception: - delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) - raise - - net_id = get_openstack_id_of_single_connected_node_by_openstack_type( - ctx, NETWORK_OPENSTACK_TYPE) - - port = { - 'name': get_resource_id(ctx, PORT_OPENSTACK_TYPE), - 'network_id': net_id, - 'security_groups': [], - } - - _handle_fixed_ips(port) - port.update(ctx.node.properties['port'], **args) - transform_resource_name(ctx, port) - - p = neutron_client.create_port({'port': port})['port'] - ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY] = p['id'] - ctx.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY] =\ - PORT_OPENSTACK_TYPE - ctx.instance.runtime_properties[OPENSTACK_NAME_PROPERTY] = p['name'] - ctx.instance.runtime_properties[FIXED_IP_ADDRESS_PROPERTY] = \ - _get_fixed_ip(p) - ctx.instance.runtime_properties[MAC_ADDRESS_PROPERTY] = p['mac_address'] - - -@operation -@with_neutron_client -def delete(neutron_client, **kwargs): - try: - delete_resource_and_runtime_properties(ctx, neutron_client, - RUNTIME_PROPERTIES_KEYS) - except neutron_exceptions.NeutronClientException, e: - if e.status_code == 404: - # port was probably deleted when an attached device was deleted - delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) - else: - raise - - -@operation -@with_nova_client -@with_neutron_client -def detach(nova_client, neutron_client, **kwargs): - - if is_external_relationship(ctx): - ctx.logger.info('Not detaching port from server since ' - 'external port and server are being used') - return - - port_id = ctx.target.instance.runtime_properties[OPENSTACK_ID_PROPERTY] - server_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY] - - server_floating_ip = get_server_floating_ip(neutron_client, server_id) - if server_floating_ip: - ctx.logger.info('We have floating ip {0} attached to server' - .format(server_floating_ip['floating_ip_address'])) - server = nova_client.servers.get(server_id) - server.remove_floating_ip(server_floating_ip['floating_ip_address']) - return ctx.operation.retry( - message='Waiting for the floating ip {0} to ' - 'detach from server {1}..' - .format(server_floating_ip['floating_ip_address'], - server_id), - retry_after=10) - change = { - 'port': { - 'device_id': '', - 'device_owner': '' - } - } - ctx.logger.info('Detaching port {0}...'.format(port_id)) - neutron_client.update_port(port_id, change) - ctx.logger.info('Successfully detached port {0}'.format(port_id)) - - -@operation -@with_neutron_client -def connect_security_group(neutron_client, **kwargs): - port_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY] - security_group_id = ctx.target.instance.runtime_properties[ - OPENSTACK_ID_PROPERTY] - - if is_external_relationship_not_conditionally_created(ctx): - ctx.logger.info('Validating external port and security-group are ' - 'connected') - if any(sg for sg in neutron_client.show_port(port_id)['port'].get( - 'security_groups', []) if sg == security_group_id): - return - raise NonRecoverableError( - 'Expected external resources port {0} and security-group {1} to ' - 'be connected'.format(port_id, security_group_id)) - - # WARNING: non-atomic operation - port = neutron_client.cosmo_get('port', id=port_id) - ctx.logger.info( - "connect_security_group(): source_id={0} target={1}".format( - port_id, ctx.target.instance.runtime_properties)) - sgs = port['security_groups'] + [security_group_id] - neutron_client.update_port(port_id, {'port': {'security_groups': sgs}}) - - # Double check if SG has been actually updated (a race-condition - # in OpenStack): - port_info = neutron_client.show_port(port_id)['port'] - port_security_groups = port_info.get('security_groups', []) - if security_group_id not in port_security_groups: - return ctx.operation.retry( - message='Security group connection (`{0}\' -> `{1}\')' - ' has not been established!'.format(port_id, - security_group_id), - retry_after=NO_SG_PORT_CONNECTION_RETRY_INTERVAL - ) - - -@operation -@with_neutron_client -def creation_validation(neutron_client, **kwargs): - validate_resource(ctx, neutron_client, PORT_OPENSTACK_TYPE) - - -def _get_fixed_ip(port): - # a port may have no fixed IP if it's set on a network without subnets - return port['fixed_ips'][0]['ip_address'] if port['fixed_ips'] else None - - -def _handle_fixed_ips(port): - fixed_ips_element = {} - - # checking for fixed ip property - if ctx.node.properties['fixed_ip']: - fixed_ips_element['ip_address'] = ctx.node.properties['fixed_ip'] - - # checking for a connected subnet - subnet_id = get_openstack_id_of_single_connected_node_by_openstack_type( - ctx, SUBNET_OPENSTACK_TYPE, if_exists=True) - if subnet_id: - fixed_ips_element['subnet_id'] = subnet_id - - # applying fixed ip parameter, if available - if fixed_ips_element: - port['fixed_ips'] = [fixed_ips_element] diff --git a/aria/multivim-plugin/neutron_plugin/router.py b/aria/multivim-plugin/neutron_plugin/router.py deleted file mode 100644 index 1a2851e4bc..0000000000 --- a/aria/multivim-plugin/neutron_plugin/router.py +++ /dev/null @@ -1,215 +0,0 @@ -######### -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# 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 warnings - -from cloudify import ctx -from cloudify.decorators import operation -from cloudify.exceptions import NonRecoverableError - -from openstack_plugin_common import ( - provider, - transform_resource_name, - get_resource_id, - with_neutron_client, - use_external_resource, - is_external_relationship, - is_external_relationship_not_conditionally_created, - delete_runtime_properties, - get_openstack_ids_of_connected_nodes_by_openstack_type, - delete_resource_and_runtime_properties, - get_resource_by_name_or_id, - validate_resource, - COMMON_RUNTIME_PROPERTIES_KEYS, - OPENSTACK_ID_PROPERTY, - OPENSTACK_TYPE_PROPERTY, - OPENSTACK_NAME_PROPERTY -) - -from neutron_plugin.network import NETWORK_OPENSTACK_TYPE - - -ROUTER_OPENSTACK_TYPE = 'router' - -# Runtime properties -RUNTIME_PROPERTIES_KEYS = COMMON_RUNTIME_PROPERTIES_KEYS - - -@operation -@with_neutron_client -def create(neutron_client, args, **kwargs): - - if use_external_resource(ctx, neutron_client, ROUTER_OPENSTACK_TYPE): - try: - ext_net_id_by_rel = _get_connected_ext_net_id(neutron_client) - - if ext_net_id_by_rel: - router_id = \ - ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY] - - router = neutron_client.show_router(router_id)['router'] - if not (router['external_gateway_info'] and 'network_id' in - router['external_gateway_info'] and - router['external_gateway_info']['network_id'] == - ext_net_id_by_rel): - raise NonRecoverableError( - 'Expected external resources router {0} and ' - 'external network {1} to be connected'.format( - router_id, ext_net_id_by_rel)) - return - except Exception: - delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) - raise - - router = { - 'name': get_resource_id(ctx, ROUTER_OPENSTACK_TYPE), - } - router.update(ctx.node.properties['router'], **args) - transform_resource_name(ctx, router) - - _handle_external_network_config(router, neutron_client) - - r = neutron_client.create_router({'router': router})['router'] - - ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY] = r['id'] - ctx.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY] =\ - ROUTER_OPENSTACK_TYPE - ctx.instance.runtime_properties[OPENSTACK_NAME_PROPERTY] = r['name'] - - -@operation -@with_neutron_client -def connect_subnet(neutron_client, **kwargs): - router_id = ctx.target.instance.runtime_properties[OPENSTACK_ID_PROPERTY] - subnet_id = ctx.source.instance.runtime_properties[OPENSTACK_ID_PROPERTY] - - if is_external_relationship_not_conditionally_created(ctx): - ctx.logger.info('Validating external subnet and router ' - 'are associated') - for port in neutron_client.list_ports(device_id=router_id)['ports']: - for fixed_ip in port.get('fixed_ips', []): - if fixed_ip.get('subnet_id') == subnet_id: - return - raise NonRecoverableError( - 'Expected external resources router {0} and subnet {1} to be ' - 'connected'.format(router_id, subnet_id)) - - neutron_client.add_interface_router(router_id, {'subnet_id': subnet_id}) - - -@operation -@with_neutron_client -def disconnect_subnet(neutron_client, **kwargs): - if is_external_relationship(ctx): - ctx.logger.info('Not connecting subnet and router since external ' - 'subnet and router are being used') - return - - neutron_client.remove_interface_router( - ctx.target.instance.runtime_properties[OPENSTACK_ID_PROPERTY], { - 'subnet_id': ctx.source.instance.runtime_properties[ - OPENSTACK_ID_PROPERTY] - } - ) - - -@operation -@with_neutron_client -def delete(neutron_client, **kwargs): - delete_resource_and_runtime_properties(ctx, neutron_client, - RUNTIME_PROPERTIES_KEYS) - - -@operation -@with_neutron_client -def creation_validation(neutron_client, **kwargs): - validate_resource(ctx, neutron_client, ROUTER_OPENSTACK_TYPE) - - -def _insert_ext_net_id_to_router_config(ext_net_id, router): - router['external_gateway_info'] = router.get( - 'external_gateway_info', {}) - router['external_gateway_info']['network_id'] = ext_net_id - - -def _handle_external_network_config(router, neutron_client): - # attempting to find an external network for the router to connect to - - # first by either a network name or id passed in explicitly; then by a - # network connected by a relationship; with a final optional fallback to an - # external network set in the Provider-context. Otherwise the router will - # simply not get connected to an external network - - provider_context = provider(ctx) - - ext_net_id_by_rel = _get_connected_ext_net_id(neutron_client) - ext_net_by_property = ctx.node.properties['external_network'] - - # the following is meant for backwards compatibility with the - # 'network_name' sugaring - if 'external_gateway_info' in router and 'network_name' in \ - router['external_gateway_info']: - warnings.warn( - 'Passing external "network_name" inside the ' - 'external_gateway_info key of the "router" property is now ' - 'deprecated; Use the "external_network" property instead', - DeprecationWarning) - - ext_net_by_property = router['external_gateway_info']['network_name'] - del (router['external_gateway_info']['network_name']) - - # need to check if the user explicitly passed network_id in the external - # gateway configuration as it affects external network behavior by - # relationship and/or provider context - if 'external_gateway_info' in router and 'network_id' in \ - router['external_gateway_info']: - ext_net_by_property = router['external_gateway_info']['network_name'] - - if ext_net_by_property and ext_net_id_by_rel: - raise RuntimeError( - "Router can't have an external network connected by both a " - 'relationship and by a network name/id') - - if ext_net_by_property: - ext_net_id = get_resource_by_name_or_id( - ext_net_by_property, NETWORK_OPENSTACK_TYPE, neutron_client)['id'] - _insert_ext_net_id_to_router_config(ext_net_id, router) - elif ext_net_id_by_rel: - _insert_ext_net_id_to_router_config(ext_net_id_by_rel, router) - elif ctx.node.properties['default_to_managers_external_network'] and \ - provider_context.ext_network: - _insert_ext_net_id_to_router_config(provider_context.ext_network['id'], - router) - - -def _check_if_network_is_external(neutron_client, network_id): - return neutron_client.show_network( - network_id)['network']['router:external'] - - -def _get_connected_ext_net_id(neutron_client): - ext_net_ids = \ - [net_id - for net_id in - get_openstack_ids_of_connected_nodes_by_openstack_type( - ctx, NETWORK_OPENSTACK_TYPE) if - _check_if_network_is_external(neutron_client, net_id)] - - if len(ext_net_ids) > 1: - raise NonRecoverableError( - 'More than one external network is connected to router {0}' - ' by a relationship; External network IDs: {0}'.format( - ext_net_ids)) - - return ext_net_ids[0] if ext_net_ids else None diff --git a/aria/multivim-plugin/neutron_plugin/security_group.py b/aria/multivim-plugin/neutron_plugin/security_group.py deleted file mode 100644 index 5f335f482b..0000000000 --- a/aria/multivim-plugin/neutron_plugin/security_group.py +++ /dev/null @@ -1,130 +0,0 @@ -######### -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# 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. - -from time import sleep - -from requests.exceptions import RequestException - -from cloudify import ctx -from cloudify.decorators import operation -from cloudify.exceptions import NonRecoverableError -from openstack_plugin_common import ( - transform_resource_name, - with_neutron_client, - delete_resource_and_runtime_properties, -) -from openstack_plugin_common.security_group import ( - build_sg_data, - process_rules, - use_external_sg, - set_sg_runtime_properties, - delete_sg, - sg_creation_validation, - RUNTIME_PROPERTIES_KEYS -) - -DEFAULT_RULE_VALUES = { - 'direction': 'ingress', - 'ethertype': 'IPv4', - 'port_range_min': 1, - 'port_range_max': 65535, - 'protocol': 'tcp', - 'remote_group_id': None, - 'remote_ip_prefix': '0.0.0.0/0', -} - - -@operation -@with_neutron_client -def create( - neutron_client, args, - status_attempts=10, status_timeout=2, **kwargs -): - - security_group = build_sg_data(args) - if not security_group['description']: - security_group['description'] = ctx.node.properties['description'] - - sg_rules = process_rules(neutron_client, DEFAULT_RULE_VALUES, - 'remote_ip_prefix', 'remote_group_id', - 'port_range_min', 'port_range_max') - - disable_default_egress_rules = ctx.node.properties.get( - 'disable_default_egress_rules') - - if use_external_sg(neutron_client): - return - - transform_resource_name(ctx, security_group) - - sg = neutron_client.create_security_group( - {'security_group': security_group})['security_group'] - - for attempt in range(max(status_attempts, 1)): - sleep(status_timeout) - try: - neutron_client.show_security_group(sg['id']) - except RequestException as e: - ctx.logger.debug("Waiting for SG to be visible. Attempt {}".format( - attempt)) - else: - break - else: - raise NonRecoverableError( - "Timed out waiting for security_group to exist", e) - - set_sg_runtime_properties(sg, neutron_client) - - try: - if disable_default_egress_rules: - for er in _egress_rules(_rules_for_sg_id(neutron_client, - sg['id'])): - neutron_client.delete_security_group_rule(er['id']) - - for sgr in sg_rules: - sgr['security_group_id'] = sg['id'] - neutron_client.create_security_group_rule( - {'security_group_rule': sgr}) - except Exception: - try: - delete_resource_and_runtime_properties( - ctx, neutron_client, - RUNTIME_PROPERTIES_KEYS) - except Exception as e: - raise NonRecoverableError( - 'Exception while tearing down for retry', e) - raise - - -@operation -@with_neutron_client -def delete(neutron_client, **kwargs): - delete_sg(neutron_client) - - -@operation -@with_neutron_client -def creation_validation(neutron_client, **kwargs): - sg_creation_validation(neutron_client, 'remote_ip_prefix') - - -def _egress_rules(rules): - return [rule for rule in rules if rule.get('direction') == 'egress'] - - -def _rules_for_sg_id(neutron_client, id): - rules = neutron_client.list_security_group_rules()['security_group_rules'] - rules = [rule for rule in rules if rule['security_group_id'] == id] - return rules diff --git a/aria/multivim-plugin/neutron_plugin/subnet.py b/aria/multivim-plugin/neutron_plugin/subnet.py deleted file mode 100644 index 6e97c96755..0000000000 --- a/aria/multivim-plugin/neutron_plugin/subnet.py +++ /dev/null @@ -1,101 +0,0 @@ -######### -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# 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. - -from cloudify import ctx -from cloudify.decorators import operation -from cloudify.exceptions import NonRecoverableError -from openstack_plugin_common import ( - with_neutron_client, - transform_resource_name, - get_resource_id, - get_openstack_id_of_single_connected_node_by_openstack_type, - delete_resource_and_runtime_properties, - delete_runtime_properties, - use_external_resource, - validate_resource, - validate_ip_or_range_syntax, - OPENSTACK_ID_PROPERTY, - OPENSTACK_TYPE_PROPERTY, - OPENSTACK_NAME_PROPERTY, - COMMON_RUNTIME_PROPERTIES_KEYS -) - -from neutron_plugin.network import NETWORK_OPENSTACK_TYPE - -SUBNET_OPENSTACK_TYPE = 'subnet' - -# Runtime properties -RUNTIME_PROPERTIES_KEYS = COMMON_RUNTIME_PROPERTIES_KEYS - - -@operation -@with_neutron_client -def create(neutron_client, args, **kwargs): - - if use_external_resource(ctx, neutron_client, SUBNET_OPENSTACK_TYPE): - try: - net_id = \ - get_openstack_id_of_single_connected_node_by_openstack_type( - ctx, NETWORK_OPENSTACK_TYPE, True) - - if net_id: - subnet_id = \ - ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY] - - if neutron_client.show_subnet( - subnet_id)['subnet']['network_id'] != net_id: - raise NonRecoverableError( - 'Expected external resources subnet {0} and network' - ' {1} to be connected'.format(subnet_id, net_id)) - return - except Exception: - delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) - raise - - net_id = get_openstack_id_of_single_connected_node_by_openstack_type( - ctx, NETWORK_OPENSTACK_TYPE) - subnet = { - 'name': get_resource_id(ctx, SUBNET_OPENSTACK_TYPE), - 'network_id': net_id, - } - subnet.update(ctx.node.properties['subnet'], **args) - transform_resource_name(ctx, subnet) - - s = neutron_client.create_subnet({'subnet': subnet})['subnet'] - ctx.instance.runtime_properties[OPENSTACK_ID_PROPERTY] = s['id'] - ctx.instance.runtime_properties[OPENSTACK_TYPE_PROPERTY] = \ - SUBNET_OPENSTACK_TYPE - ctx.instance.runtime_properties[OPENSTACK_NAME_PROPERTY] = subnet['name'] - - -@operation -@with_neutron_client -def delete(neutron_client, **kwargs): - delete_resource_and_runtime_properties(ctx, neutron_client, - RUNTIME_PROPERTIES_KEYS) - - -@operation -@with_neutron_client -def creation_validation(neutron_client, args, **kwargs): - validate_resource(ctx, neutron_client, SUBNET_OPENSTACK_TYPE) - subnet = dict(ctx.node.properties['subnet'], **args) - - if 'cidr' not in subnet: - err = '"cidr" property must appear under the "subnet" property of a ' \ - 'subnet node' - ctx.logger.error('VALIDATION ERROR: ' + err) - raise NonRecoverableError(err) - validate_ip_or_range_syntax(ctx, subnet['cidr']) diff --git a/aria/multivim-plugin/neutron_plugin/tests/__init__.py b/aria/multivim-plugin/neutron_plugin/tests/__init__.py deleted file mode 100644 index 04cb21f745..0000000000 --- a/aria/multivim-plugin/neutron_plugin/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'idanmo' diff --git a/aria/multivim-plugin/neutron_plugin/tests/test.py b/aria/multivim-plugin/neutron_plugin/tests/test.py deleted file mode 100644 index 459c23a6cd..0000000000 --- a/aria/multivim-plugin/neutron_plugin/tests/test.py +++ /dev/null @@ -1,220 +0,0 @@ -######### -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# 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 mock -import random -import string -import unittest - -from cloudify.exceptions import NonRecoverableError -from cloudify.context import BootstrapContext - -from cloudify.mocks import MockCloudifyContext - -import openstack_plugin_common as common -import openstack_plugin_common.tests.test as common_test - -import neutron_plugin -import neutron_plugin.network -import neutron_plugin.port -import neutron_plugin.router -import neutron_plugin.security_group - - -class ResourcesRenamingTest(unittest.TestCase): - def setUp(self): - neutron_plugin.port._find_network_in_related_nodes = mock.Mock() - # *** Configs from files ******************** - common.Config.get = mock.Mock() - common.Config.get.return_value = {} - # *** Neutron ******************** - self.neutron_mock = mock.Mock() - - def neutron_mock_connect(unused_self, unused_cfg): - return self.neutron_mock - common.NeutronClient.connect = neutron_mock_connect - - self.neutron_mock.cosmo_list = mock.Mock() - self.neutron_mock.cosmo_list.return_value = [] - - def _setup_ctx(self, obj_type): - ctx = common_test.create_mock_ctx_with_provider_info( - node_id='__cloudify_id_something_001', - properties={ - obj_type: { - 'name': obj_type + '_name', - }, - 'rules': [] # For security_group - } - ) - return ctx - - def _test(self, obj_type): - ctx = self._setup_ctx(obj_type) - attr = getattr(self.neutron_mock, 'create_' + obj_type) - attr.return_value = { - obj_type: { - 'id': obj_type + '_id', - } - } - getattr(neutron_plugin, obj_type).create(ctx) - calls = attr.mock_calls - self.assertEquals(len(calls), 1) # Exactly one object created - # Indexes into call[]: - # 0 - the only call - # 1 - regular arguments - # 0 - first argument - arg = calls[0][1][0] - self.assertEquals(arg[obj_type]['name'], 'p2_' + obj_type + '_name') - - def test_network(self): - self._test('network') - - def test_port(self): - self._test('port') - - def test_router(self): - self._test('router') - - def test_security_group(self): - self._test('security_group') - - # Network chosen arbitrary for this test. - # Just testing something without prefix. - def test_network_no_prefix(self): - ctx = self._setup_ctx('network') - for pctx in common_test.BOOTSTRAP_CONTEXTS_WITHOUT_PREFIX: - ctx._bootstrap_context = BootstrapContext(pctx) - self.neutron_mock.create_network.reset_mock() - self.neutron_mock.create_network.return_value = { - 'network': { - 'id': 'network_id', - } - } - neutron_plugin.network.create(ctx) - calls = self.neutron_mock.create_network.mock_calls - self.assertEquals(len(calls), 1) # Exactly one network created - # Indexes into call[]: - # 0 - the only call - # 1 - regular arguments - # 0 - first argument - arg = calls[0][1][0] - self.assertEquals(arg['network']['name'], 'network_name', - "Failed with context: " + str(pctx)) - - -def _rand_str(n): - chars = string.ascii_uppercase + string.digits - return ''.join(random.choice(chars) for _ in range(n)) - - -class SecurityGroupTest(unittest.TestCase): - def setUp(self): - # *** Configs from files ******************** - common.Config.get = mock.Mock() - common.Config.get.return_value = {} - # *** Neutron ******************** - self.neutron_mock = mock.Mock() - - def neutron_mock_connect(unused_self, unused_cfg): - return self.neutron_mock - common.NeutronClient.connect = neutron_mock_connect - neutron_plugin.security_group._rules_for_sg_id = mock.Mock() - neutron_plugin.security_group._rules_for_sg_id.return_value = [] - - def _setup_ctx(self): - sg_name = _rand_str(6) + '_new' - ctx = MockCloudifyContext(properties={ - 'security_group': { - 'name': sg_name, - 'description': 'blah' - }, - 'rules': [{'port': 80}], - 'disable_default_egress_rules': True, - }) - return ctx - - def test_sg_new(self): - ctx = self._setup_ctx() - self.neutron_mock.cosmo_list = mock.Mock() - self.neutron_mock.cosmo_list.return_value = [] - self.neutron_mock.create_security_group = mock.Mock() - self.neutron_mock.create_security_group.return_value = { - 'security_group': { - 'description': 'blah', - 'id': ctx['security_group']['name'] + '_id', - } - } - neutron_plugin.security_group.create(ctx) - self.assertTrue(self.neutron_mock.create_security_group.mock_calls) - - def test_sg_use_existing(self): - ctx = self._setup_ctx() - self.neutron_mock.cosmo_list = mock.Mock() - self.neutron_mock.cosmo_list.return_value = [{ - 'id': ctx['security_group']['name'] + '_existing_id', - 'description': 'blah', - 'security_group_rules': [{ - 'remote_group_id': None, - 'direction': 'ingress', - 'protocol': 'tcp', - 'ethertype': 'IPv4', - 'port_range_max': 80, - 'port_range_min': 80, - 'remote_ip_prefix': '0.0.0.0/0', - }] - }] - self.neutron_mock.create_security_group = mock.Mock() - self.neutron_mock.create_security_group.return_value = { - 'security_group': { - 'description': 'blah', - 'id': ctx['security_group']['name'] + '_id', - } - } - neutron_plugin.security_group.create(ctx) - self.assertFalse(self.neutron_mock.create_security_group.mock_calls) - - def test_sg_use_existing_with_other_rules(self): - ctx = self._setup_ctx() - self.neutron_mock.cosmo_list = mock.Mock() - self.neutron_mock.cosmo_list.return_value = [{ - 'id': ctx['security_group']['name'] + '_existing_id', - 'description': 'blah', - 'security_group_rules': [{ - 'remote_group_id': None, - 'direction': 'ingress', - 'protocol': 'tcp', - 'ethertype': 'IPv4', - 'port_range_max': 81, # Note the different port! - 'port_range_min': 81, # Note the different port! - 'remote_ip_prefix': '0.0.0.0/0', - }] - }] - self.neutron_mock.create_security_group = mock.Mock() - self.neutron_mock.create_security_group.return_value = { - 'security_group': { - 'description': 'blah', - 'id': ctx['security_group']['name'] + '_id', - } - } - self.assertRaises( - NonRecoverableError, - neutron_plugin.security_group.create, - ctx - ) - - -if __name__ == '__main__': - unittest.main() diff --git a/aria/multivim-plugin/neutron_plugin/tests/test_port.py b/aria/multivim-plugin/neutron_plugin/tests/test_port.py deleted file mode 100644 index 1acee3d05d..0000000000 --- a/aria/multivim-plugin/neutron_plugin/tests/test_port.py +++ /dev/null @@ -1,156 +0,0 @@ -######## -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# 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 unittest - -import mock - -import neutron_plugin.port -from cloudify.mocks import (MockCloudifyContext, - MockNodeInstanceContext, - MockRelationshipSubjectContext) -from openstack_plugin_common import (NeutronClientWithSugar, - OPENSTACK_ID_PROPERTY) -from cloudify.exceptions import OperationRetry - - -class TestPort(unittest.TestCase): - - def test_fixed_ips_no_fixed_ips(self): - node_props = {'fixed_ip': ''} - - with mock.patch( - 'neutron_plugin.port.' - 'get_openstack_id_of_single_connected_node_by_openstack_type', - self._get_connected_subnet_mock(return_empty=True)): - with mock.patch( - 'neutron_plugin.port.ctx', - self._get_mock_ctx_with_node_properties(node_props)): - - port = {} - neutron_plugin.port._handle_fixed_ips(port) - - self.assertNotIn('fixed_ips', port) - - def test_fixed_ips_subnet_only(self): - node_props = {'fixed_ip': ''} - - with mock.patch( - 'neutron_plugin.port.' - 'get_openstack_id_of_single_connected_node_by_openstack_type', - self._get_connected_subnet_mock(return_empty=False)): - with mock.patch( - 'neutron_plugin.port.ctx', - self._get_mock_ctx_with_node_properties(node_props)): - - port = {} - neutron_plugin.port._handle_fixed_ips(port) - - self.assertEquals([{'subnet_id': 'some-subnet-id'}], - port.get('fixed_ips')) - - def test_fixed_ips_ip_address_only(self): - node_props = {'fixed_ip': '1.2.3.4'} - - with mock.patch( - 'neutron_plugin.port.' - 'get_openstack_id_of_single_connected_node_by_openstack_type', - self._get_connected_subnet_mock(return_empty=True)): - with mock.patch( - 'neutron_plugin.port.ctx', - self._get_mock_ctx_with_node_properties(node_props)): - - port = {} - neutron_plugin.port._handle_fixed_ips(port) - - self.assertEquals([{'ip_address': '1.2.3.4'}], - port.get('fixed_ips')) - - def test_fixed_ips_subnet_and_ip_address(self): - node_props = {'fixed_ip': '1.2.3.4'} - - with mock.patch( - 'neutron_plugin.port.' - 'get_openstack_id_of_single_connected_node_by_openstack_type', - self._get_connected_subnet_mock(return_empty=False)): - with mock.patch( - 'neutron_plugin.port.ctx', - self._get_mock_ctx_with_node_properties(node_props)): - - port = {} - neutron_plugin.port._handle_fixed_ips(port) - - self.assertEquals([{'ip_address': '1.2.3.4', - 'subnet_id': 'some-subnet-id'}], - port.get('fixed_ips')) - - @staticmethod - def _get_connected_subnet_mock(return_empty=True): - return lambda *args, **kw: None if return_empty else 'some-subnet-id' - - @staticmethod - def _get_mock_ctx_with_node_properties(properties): - return MockCloudifyContext(node_id='test_node_id', - properties=properties) - - -class MockNeutronClient(NeutronClientWithSugar): - """A fake neutron client with hard-coded test data.""" - def __init__(self, update): - self.update = update - self.body = {'port': {'id': 'test-id', 'security_groups': []}} - - def show_port(self, *_): - return self.body - - def update_port(self, _, b, **__): - if self.update: - self.body.update(b) - return - - def cosmo_get(self, *_, **__): - return self.body['port'] - - -class TestPortSG(unittest.TestCase): - @mock.patch('openstack_plugin_common._put_client_in_kw') - def test_connect_sg_to_port(self, *_): - mock_neutron = MockNeutronClient(update=True) - ctx = MockCloudifyContext( - source=MockRelationshipSubjectContext(node=mock.MagicMock(), - instance=mock.MagicMock()), - target=MockRelationshipSubjectContext(node=mock.MagicMock(), - instance=mock.MagicMock())) - - with mock.patch('neutron_plugin.port.ctx', ctx): - neutron_plugin.port.connect_security_group(mock_neutron) - self.assertIsNone(ctx.operation._operation_retry) - - @mock.patch('openstack_plugin_common._put_client_in_kw') - def test_connect_sg_to_port_race_condition(self, *_): - mock_neutron = MockNeutronClient(update=False) - - ctx = MockCloudifyContext( - source=MockRelationshipSubjectContext(node=mock.MagicMock(), - instance=mock.MagicMock()), - target=MockRelationshipSubjectContext( - node=mock.MagicMock(), - instance=MockNodeInstanceContext( - runtime_properties={ - OPENSTACK_ID_PROPERTY: 'test-sg-id'}))) - with mock.patch('neutron_plugin.port.ctx', ctx): - neutron_plugin.port.connect_security_group(mock_neutron, ctx=ctx) - self.assertIsInstance(ctx.operation._operation_retry, - OperationRetry) diff --git a/aria/multivim-plugin/neutron_plugin/tests/test_security_group.py b/aria/multivim-plugin/neutron_plugin/tests/test_security_group.py deleted file mode 100644 index e958cddb33..0000000000 --- a/aria/multivim-plugin/neutron_plugin/tests/test_security_group.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -######### -# Copyright (c) 2016 GigaSpaces Technologies Ltd. All rights reserved -# -# 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 unittest - -from mock import Mock, patch -from requests.exceptions import RequestException - -from neutron_plugin import security_group - -from cloudify.exceptions import NonRecoverableError -from cloudify.state import current_ctx - -from cloudify.mocks import MockCloudifyContext - - -class FakeException(Exception): - pass - - -@patch('openstack_plugin_common.OpenStackClient._validate_auth_params') -@patch('openstack_plugin_common.NeutronClientWithSugar') -class TestSecurityGroup(unittest.TestCase): - - def setUp(self): - super(TestSecurityGroup, self).setUp() - self.nova_client = Mock() - - self.ctx = MockCloudifyContext( - node_id='test', - deployment_id='test', - properties={ - 'description': 'The best Security Group. Great', - 'rules': [], - 'resource_id': 'mock_sg', - 'security_group': { - }, - 'server': {}, - 'openstack_config': { - 'auth_url': 'things/v3', - }, - }, - operation={'retry_number': 0}, - provider_context={'resources': {}} - ) - current_ctx.set(self.ctx) - self.addCleanup(current_ctx.clear) - - findctx = patch( - 'openstack_plugin_common._find_context_in_kw', - return_value=self.ctx, - ) - findctx.start() - self.addCleanup(findctx.stop) - - def test_set_sg_runtime_properties(self, mock_nc, *_): - security_group.create( - nova_client=self.nova_client, - ctx=self.ctx, - args={}, - ) - - self.assertEqual( - { - 'external_type': 'security_group', - 'external_id': mock_nc().get_id_from_resource(), - 'external_name': mock_nc().get_name_from_resource(), - }, - self.ctx.instance.runtime_properties - ) - - def test_create_sg_wait_timeout(self, mock_nc, *_): - mock_nc().show_security_group.side_effect = RequestException - - with self.assertRaises(NonRecoverableError): - security_group.create( - nova_client=self.nova_client, - ctx=self.ctx, - args={}, - status_attempts=3, - status_timeout=0.001, - ) - - @patch( - 'neutron_plugin.security_group.delete_resource_and_runtime_properties') - def test_dont_duplicate_if_failed_rule(self, mock_del_res, mock_nc, *_): - self.ctx.node.properties['rules'] = [ - { - 'port': '🍷', - }, - ] - mock_nc().create_security_group_rule.side_effect = FakeException - mock_del_res.side_effect = FakeException('the 2nd') - - with self.assertRaises(NonRecoverableError) as e: - security_group.create( - nova_client=self.nova_client, - ctx=self.ctx, - args={}, - ) - - self.assertIn('the 2nd', str(e.exception)) |