Exemplo n.º 1
0
class SubnetExtensionDriver(api.ExtensionDriver):
    """Subnet extension driver to process and extend AZ data."""

    _supported_extension_alias = 'subnet_availability_zone'

    def initialize(self):
        """Initialize subnet extension driver."""
        self.subnet_cache = {}
        self.vpc_cidr_cache = {}
        self.aws_obj = AwsUtils()
        self.physical_network_cache = {}
        self.ec2_client_cache = {}
        self.ec2_cache_timer = datetime.datetime.now()
        self._ks_session = None
        LOG.info("SubnetExtensionDriver initialization complete")

    @property
    def ks_session(self):
        if self._ks_session is None:
            self._ks_session = self.aws_obj.get_keystone_session()
        return self._ks_session

    def get_context(self, project_id):
        ctx = context.Context(tenant_id=project_id)
        ctx.auth_token = self.ks_session.get_token()
        return ctx

    def get_ec2_client(self, project_id):
        tdiff = datetime.datetime.now() - self.ec2_cache_timer
        if tdiff.total_seconds() > 900:
            self.ec2_cache_timer = datetime.datetime.now()
            self.ec2_client_cache = {}
        if project_id in self.ec2_client_cache:
            return self.ec2_client_cache[project_id]
        ctx = self.get_context(project_id=project_id)
        ec2_client = self.aws_obj._get_ec2_client(ctx, project_id=project_id)
        self.ec2_client_cache[project_id] = ec2_client
        return ec2_client

    def _get_phynet_from_network(self, network_id, tenant_id):
        if network_id in self.physical_network_cache:
            return self.physical_network_cache[network_id]

        ctx = self.get_context(project_id=tenant_id)
        try:
            plugin = directory.get_plugin()
            network = plugin.get_network(ctx, network_id)
            if providernet.PHYSICAL_NETWORK in network:
                phy_net = network[providernet.PHYSICAL_NETWORK]
                self.physical_network_cache[network_id] = phy_net
                return phy_net
        except Exception as e:
            LOG.exception(e)
        return None

    @property
    def extension_alias(self):
        """Extension alias to load extension."""
        return self._supported_extension_alias

    def process_create_subnet(self, plugin_context, data, result):
        """Set AZ data in result to use in AWS mechanism."""
        result[az_ext.RESOURCE_NAME] = data[az_ext.RESOURCE_NAME]
        self.subnet_cache[result['id']] = data[az_ext.RESOURCE_NAME]

    def _check_for_vpc_cidr(self, vpc, result):
        cidr = result['cidr']
        if (vpc, cidr) in self.vpc_cidr_cache:
            result[az_ext.RESOURCE_NAME] = self.vpc_cidr_cache[(vpc, cidr)]
            return True
        project_id = result['tenant_id']
        ec2_client = self.get_ec2_client(project_id)
        response = ec2_client.describe_subnets(Filters=[
            {
                'Name': 'vpc-id',
                'Values': [vpc]
            },
        ])
        if 'Subnets' in response:
            for subnet in response['Subnets']:
                self.vpc_cidr_cache[(subnet['VpcId'], subnet['CidrBlock'])] = \
                    subnet['AvailabilityZone']
        if (vpc, cidr) in self.vpc_cidr_cache:
            result[az_ext.RESOURCE_NAME] = self.vpc_cidr_cache[(vpc, cidr)]
            return True
        return False

    def _check_for_openstack_subnet(self, result):
        ostack_id = result['id']
        if ostack_id in self.subnet_cache:
            result[az_ext.RESOURCE_NAME] = self.subnet_cache[ostack_id]
            return True
        project_id = result['tenant_id']
        ec2_client = self.get_ec2_client(project_id)
        response = ec2_client.describe_subnets(Filters=[{
            'Name': 'tag-value',
            'Values': [ostack_id]
        }])
        if 'Subnets' in response:
            for subnet in response['Subnets']:
                if 'SubnetId' in subnet:
                    self.subnet_cache[ostack_id] = subnet['AvailabilityZone']
                    result[az_ext.RESOURCE_NAME] = subnet['AvailabilityZone']
                    return True
        return False

    def extend_subnet_dict(self, session, db_data, result):
        """Extend subnet dict."""
        phynet = self._get_phynet_from_network(result['network_id'],
                                               result['tenant_id'])
        if isinstance(phynet, six.string_types):
            if phynet == 'external':
                return
            elif phynet.startswith('vpc') and self._check_for_vpc_cidr(
                    phynet, result):
                return
        if self._check_for_openstack_subnet(result):
            return
Exemplo n.º 2
0
class AwsMechanismDriver(api.MechanismDriver):
    """Ml2 Mechanism driver for AWS"""
    def __init__(self):
        self.aws_utils = None
        self._default_sgr_to_remove = []
        super(AwsMechanismDriver, self).__init__()

    def initialize(self):
        self.aws_utils = AwsUtils()
        callbacks.subscribe(self)

    # NETWORK
    def create_network_precommit(self, context):
        pass

    def create_network_postcommit(self, context):
        pass

    def update_network_precommit(self, context):
        try:
            network_name = context.current['name']
            original_network_name = context.original['name']
            LOG.debug("Update network original: %s current: %s",
                      original_network_name, network_name)

            if network_name == original_network_name:
                return
            neutron_network_id = context.current['id']
            project_id = context.current['project_id']
            tags_list = [{'Key': 'Name', 'Value': network_name}]
            self.aws_utils.create_tags_for_vpc(neutron_network_id,
                                               tags_list,
                                               context=context._plugin_context,
                                               project_id=project_id)
        except Exception as e:
            LOG.error("Error in update subnet precommit: %s" % e)
            raise e

    def update_network_postcommit(self, context):
        pass

    def delete_network_precommit(self, context):
        neutron_network_id = context.current['id']
        project_id = context.current['project_id']
        # If user is deleting an empty  neutron network then nothing to be done
        # on AWS side
        if len(context.current['subnets']) > 0:
            vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(
                neutron_network_id,
                context=context._plugin_context,
                project_id=project_id)
            if vpc_id is not None:
                LOG.info("Deleting network %s (VPC_ID: %s)" %
                         (neutron_network_id, vpc_id))
                try:
                    self.aws_utils.delete_vpc(vpc_id=vpc_id,
                                              context=context._plugin_context,
                                              project_id=project_id)
                except AwsException as e:
                    if 'InvalidVpcID.NotFound' in e.msg:
                        LOG.warn(e.msg)
                    else:
                        raise e
                omni_resources.delete_mapping(context.current['id'])

    def delete_network_postcommit(self, context):
        pass

    # SUBNET
    def create_subnet_precommit(self, context):
        network_id = context.network.current['id']
        LOG.info("Create subnet for network %s" % network_id)
        # External Network doesn't exist on AWS, so no operations permitted
        physical_network = context.network.current.get(
            'provider:physical_network')
        if physical_network == "external":
            # Do not create subnets for external & provider networks. Only
            # allow tenant network subnet creation at the moment.
            LOG.info('Creating external network {0}'.format(network_id))
            return
        elif physical_network and physical_network.startswith('vpc'):
            LOG.info('Registering AWS network with vpc %s', physical_network)
            subnet_cidr = context.current['cidr']
            subnet_id = self.aws_utils.get_subnet_from_vpc_and_cidr(
                context._plugin_context, physical_network, subnet_cidr,
                context.current['project_id'])
            omni_resources.add_mapping(network_id, physical_network)
            omni_resources.add_mapping(context.current['id'], subnet_id)
            return
        if context.current['ip_version'] == 6:
            raise AwsException(error_code="IPv6Error",
                               message="Cannot create subnets with IPv6")
        mask = int(context.current['cidr'][-2:])
        if mask < 16 or mask > 28:
            raise AwsException(error_code="InvalidMask",
                               message="Subnet mask has to be >16 and <28")
        try:
            # Check if this is the first subnet to be added to a network
            neutron_network = context.network.current
            associated_vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(
                neutron_network['id'], context=context._plugin_context)
            if associated_vpc_id is None:
                # Need to create EC2 VPC
                vpc_cidr = context.current['cidr'][:-2] + '16'
                tags = [{
                    'Key': 'Name',
                    'Value': neutron_network['name']
                }, {
                    'Key': 'openstack_network_id',
                    'Value': neutron_network['id']
                }, {
                    'Key': 'openstack_tenant_id',
                    'Value': context.current['tenant_id']
                }]
                associated_vpc_id = self.aws_utils.create_vpc_and_tags(
                    cidr=vpc_cidr,
                    tags_list=tags,
                    context=context._plugin_context)
                omni_resources.add_mapping(neutron_network['id'],
                                           associated_vpc_id)
            # Create Subnet in AWS
            tags = [{
                'Key': 'Name',
                'Value': context.current['name']
            }, {
                'Key': 'openstack_subnet_id',
                'Value': context.current['id']
            }, {
                'Key': 'openstack_tenant_id',
                'Value': context.current['tenant_id']
            }]
            if AZ in context.current and context.current[AZ]:
                aws_az = context.current[AZ]
            elif context.network.current[AZ_HINT]:
                network_az_hints = context.network.current[AZ_HINT]
                if len(network_az_hints) > 1:
                    # We use only one AZ hint even if multiple AZ values
                    # are passed while creating network.
                    raise NetworkWithMultipleAZs()
                aws_az = network_az_hints[0]
            else:
                raise AzNotProvided()
            self._validate_az(aws_az)
            ec2_subnet_id = self.aws_utils.create_subnet_and_tags(
                vpc_id=associated_vpc_id,
                cidr=context.current['cidr'],
                tags_list=tags,
                aws_az=aws_az,
                context=context._plugin_context)
            omni_resources.add_mapping(context.current['id'], ec2_subnet_id)
        except Exception as e:
            LOG.error("Error in create subnet precommit: %s" % e)
            raise e

    def _send_request(self, session, url):
        headers = {
            'Content-Type': 'application/json',
            'X-Auth-Token': session.get_token()
        }
        response = requests.get(url + "/v1/zones", headers=headers)
        response.raise_for_status()
        return response.json()

    def _validate_az(self, aws_az):
        if not isinstance(aws_az, six.string_types):
            raise InvalidAzValue()
        if ',' in aws_az:
            raise NetworkWithMultipleAZs()
        session = self.aws_utils.get_keystone_session()
        azmgr_url = session.get_endpoint(service_type='azmanager',
                                         region_name=cfg.CONF.nova_region_name)
        zones = self._send_request(session, azmgr_url)
        if aws_az not in zones:
            LOG.error("Provided az %s not found in zones %s", aws_az, zones)
            raise InvalidAzValue()

    def create_subnet_postcommit(self, context):
        pass

    def update_subnet_precommit(self, context):
        try:
            subnet_name = context.current['name']
            neutron_subnet_id = context.current['id']
            tags_list = [{'Key': 'Name', 'Value': subnet_name}]
            self.aws_utils.create_subnet_tags(neutron_subnet_id,
                                              tags_list,
                                              context=context._plugin_context)
        except Exception as e:
            LOG.error("Error in update subnet precommit: %s" % e)
            raise e

    def update_subnet_postcommit(self, context):
        pass

    def delete_subnet_precommit(self, context):
        try:
            LOG.info("Deleting subnet %s" % context.current['id'])
            project_id = context.current['project_id']
            subnet_id = self.aws_utils.get_subnet_from_neutron_subnet_id(
                context.current['id'],
                context=context._plugin_context,
                project_id=project_id)
            if not subnet_id:
                raise Exception("Subnet mapping %s not found" %
                                (context.current['id']))
            try:
                self.aws_utils.delete_subnet(subnet_id=subnet_id,
                                             context=context._plugin_context,
                                             project_id=project_id)
                omni_resources.delete_mapping(context.current['id'])
            except AwsException as e:
                if 'InvalidSubnetID.NotFound' in e.msg:
                    LOG.warn(e.msg)
                    omni_resources.delete_mapping(context.current['id'])
                else:
                    raise e
        except Exception as e:
            LOG.error("Error in delete subnet precommit: %s" % e)
            raise e

    def delete_subnet_postcommit(self, context):
        neutron_network = context.network.current
        try:
            subnets = neutron_network['subnets']
            if (len(subnets) == 1 and subnets[0] == context.current['id']
                    or len(subnets) == 0):
                # Last subnet for this network was deleted, so delete VPC
                # because VPC gets created during first subnet creation under
                # an OpenStack network
                project_id = context.current['project_id']
                vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(
                    neutron_network['id'],
                    context=context._plugin_context,
                    project_id=project_id)
                if not vpc_id:
                    raise Exception("Network mapping %s not found",
                                    neutron_network['id'])
                LOG.info("Deleting VPC %s since this was the last subnet in "
                         "the vpc" % vpc_id)
                self.aws_utils.delete_vpc(vpc_id=vpc_id,
                                          context=context._plugin_context,
                                          project_id=project_id)
                omni_resources.delete_mapping(context.network.current['id'])
        except Exception as e:
            LOG.error("Error in delete subnet postcommit: %s" % e)
            raise e

    def create_port_precommit(self, context):
        pass

    def create_port_postcommit(self, context):
        pass

    def update_port_precommit(self, context):
        original_port = context._original_port
        updated_port = context._port
        sorted_original_sgs = sorted(original_port['security_groups'])
        sorted_updated_sgs = sorted(updated_port['security_groups'])
        aws_sgs = []
        project_id = context.current['project_id']
        if sorted_updated_sgs != sorted_original_sgs:
            for sg in updated_port['security_groups']:
                aws_secgrps = self.aws_utils.get_sec_group_by_id(
                    sg, context._plugin_context, project_id=project_id)
                aws_sgs.append(aws_secgrps[0]['GroupId'])
        if aws_sgs:
            self.aws_utils.modify_ports(aws_sgs, updated_port['name'],
                                        context._plugin_context, project_id)

    def update_port_postcommit(self, context):
        pass

    def delete_port_precommit(self, context):
        pass

    def delete_port_postcommit(self, context):
        pass

    def bind_port(self, context):
        fixed_ip_dict = dict()
        if 'fixed_ips' in context.current:
            if len(context.current['fixed_ips']) > 0:
                fixed_ip_dict = context.current['fixed_ips'][0]
                openstack_subnet_id = fixed_ip_dict['subnet_id']
                aws_subnet_id = \
                    self.aws_utils.get_subnet_from_neutron_subnet_id(
                        openstack_subnet_id, context._plugin_context,
                        project_id=context.current['project_id'])
                fixed_ip_dict['subnet_id'] = aws_subnet_id
                secgroup_ids = context.current['security_groups']
                ec2_secgroup_ids = self.create_security_groups_if_needed(
                    context, secgroup_ids)
                fixed_ip_dict['ec2_security_groups'] = ec2_secgroup_ids
        segment_id = random.choice(context.network.network_segments)[api.ID]
        context.set_binding(segment_id,
                            "vip_type_a",
                            json.dumps(fixed_ip_dict),
                            status='ACTIVE')
        return True

    def create_security_groups_if_needed(self, context, secgrp_ids):
        project_id = context.current.get('project_id')
        core_plugin = directory.get_plugin()
        vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(
            context.current['network_id'],
            context=context._plugin_context,
            project_id=project_id)
        ec2_secgroup_ids = []
        for secgrp_id in secgrp_ids:
            tags = [{
                'Key': 'openstack_id',
                'Value': secgrp_id
            }, {
                'Key': 'openstack_network_id',
                'Value': context.current['network_id']
            }]
            secgrp = core_plugin.get_security_group(context._plugin_context,
                                                    secgrp_id)
            aws_secgrps = self.aws_utils.get_sec_group_by_id(
                secgrp_id,
                group_name=secgrp['name'],
                vpc_id=vpc_id,
                context=context._plugin_context,
                project_id=project_id)
            if not aws_secgrps and secgrp['name'] != 'default':
                grp_name = secgrp['name']
                tags.append({"Key": "Name", "Value": grp_name})
                desc = secgrp['description']
                rules = secgrp['security_group_rules']
                ec2_secgrp = self.aws_utils.create_security_group(
                    grp_name,
                    desc,
                    vpc_id,
                    secgrp_id,
                    tags,
                    context=context._plugin_context,
                    project_id=project_id)
                self.aws_utils.create_security_group_rules(ec2_secgrp, rules)
                # Make sure that omni_resources table is populated with newly
                # created security group
                aws_secgrps = self.aws_utils.get_sec_group_by_id(
                    secgrp_id,
                    group_name=secgrp['name'],
                    vpc_id=vpc_id,
                    context=context._plugin_context,
                    project_id=project_id)
            for aws_secgrp in aws_secgrps:
                ec2_secgroup_ids.append(aws_secgrp['GroupId'])
        return ec2_secgroup_ids

    def delete_security_group(self, security_group_id, context, project_id):
        core_plugin = directory.get_plugin()
        secgrp = core_plugin.get_security_group(context, security_group_id)
        self.aws_utils.delete_security_group(security_group_id,
                                             context,
                                             project_id,
                                             group_name=secgrp['name'])

    def remove_security_group_rule(self, context, rule_id):
        core_plugin = directory.get_plugin()
        rule = core_plugin.get_security_group_rule(context, rule_id)
        secgrp_id = rule['security_group_id']
        secgrp = core_plugin.get_security_group(context, secgrp_id)
        if "project_id" in rule:
            project_id = rule['project_id']
        else:
            project_id = context.tenant
        self.aws_utils.delete_security_group_rule_if_needed(
            context, secgrp_id, secgrp['name'], project_id, rule)

    def add_security_group_rule(self, context, rule):
        core_plugin = directory.get_plugin()
        secgrp_id = rule['security_group_id']
        secgrp = core_plugin.get_security_group(context, secgrp_id)
        if "project_id" in rule:
            project_id = rule['project_id']
        else:
            project_id = context.tenant
        self.aws_utils.create_security_group_rule_if_needed(
            context, secgrp_id, secgrp['name'], project_id, rule)

    def update_security_group_rules(self, context, rule_id):
        core_plugin = directory.get_plugin()
        rule = core_plugin.get_security_group_rule(context, rule_id)
        secgrp_id = rule['security_group_id']
        secgrp = core_plugin.get_security_group(context, secgrp_id)
        old_rules = secgrp['security_group_rules']
        for idx in range(len(old_rules) - 1, -1, -1):
            if old_rules[idx]['id'] == rule_id:
                old_rules.pop(idx)
                break
        old_rules.append(rule)
        if "project_id" in rule:
            project_id = rule['project_id']
        else:
            project_id = context.tenant
        self.aws_utils.update_sec_group(secgrp_id,
                                        old_rules,
                                        context=context,
                                        project_id=project_id,
                                        group_name=secgrp['name'])

    def secgroup_callback(self, resource, event, trigger, **kwargs):
        context = kwargs['context']
        if resource == resources.SECURITY_GROUP:
            if event == events.AFTER_CREATE:
                project_id = kwargs.get('security_group')['project_id']
                secgrp = kwargs.get('security_group')
                security_group_id = secgrp.get('id')
                core_plugin = directory.get_plugin()
                aws_secgrps = self.aws_utils.get_sec_group_by_id(
                    security_group_id,
                    group_name=secgrp.get('name'),
                    context=context,
                    project_id=project_id)
                if len(aws_secgrps) == 0:
                    return
                for sgr in secgrp.get('security_group_rules', []):
                    # This is invoked for discovered security groups only. For
                    # discovered security groups we do not need default egress
                    # rules. Those should be reported by discovery service.
                    # When removing these default security group rules we do
                    # not need to check against AWS. Store the security group
                    # rule IDs so that we can ignore them when delete security
                    # group rule is called here.
                    self._default_sgr_to_remove.append(sgr.get('id'))
                    core_plugin.delete_security_group_rule(
                        context, sgr.get('id'))
            if event == events.BEFORE_DELETE:
                project_id = kwargs.get('security_group')['project_id']
                security_group_id = kwargs.get('security_group_id')
                if security_group_id:
                    self.delete_security_group(security_group_id, context,
                                               project_id)
                else:
                    LOG.warn('Security group ID not found in delete request')
        elif resource == resources.SECURITY_GROUP_RULE:
            if event == events.BEFORE_CREATE:
                rule = kwargs['security_group_rule']
                self.add_security_group_rule(context, rule)
            elif event == events.BEFORE_DELETE:
                rule_id = kwargs['security_group_rule_id']
                if rule_id in self._default_sgr_to_remove:
                    # Check the comment above in security group rule
                    # AFTER_CREATE event handling
                    self._default_sgr_to_remove.remove(rule_id)
                else:
                    self.remove_security_group_rule(context, rule_id)
            elif event == events.BEFORE_UPDATE:
                rule_id = kwargs['security_group_rule_id']
                self.update_security_group_rules(context, rule_id)