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