######### # 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