# Copyright 2016 Platform9 Systems Inc.(http://www.platform9.com) # # 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 neutron.common import constants as n_const from neutron.db import common_db_mixin from neutron.db import extraroute_db from neutron.db import l3_db from neutron.db import l3_dvrscheduler_db from neutron.db import l3_gwmode_db from neutron.db import l3_hamode_db from neutron.db import l3_hascheduler_db from neutron.plugins.common import constants from neutron.quota import resource_registry from neutron.services import service_base from oslo_log import log as logging from neutron.common import exceptions from neutron.db import securitygroups_db from neutron.common.aws_utils import AwsUtils LOG = logging.getLogger(__name__) class AwsRouterPlugin(service_base.ServicePluginBase, common_db_mixin.CommonDbMixin, extraroute_db.ExtraRoute_db_mixin, l3_hamode_db.L3_HA_NAT_db_mixin, l3_gwmode_db.L3_NAT_db_mixin, l3_dvrscheduler_db.L3_DVRsch_db_mixin, l3_hascheduler_db.L3_HA_scheduler_db_mixin): """Implementation of the Neutron L3 Router Service Plugin. This class implements a L3 service plugin that provides router and floatingip resources and manages associated request/response. All DB related work is implemented in classes l3_db.L3_NAT_db_mixin, l3_hamode_db.L3_HA_NAT_db_mixin, l3_dvr_db.L3_NAT_with_dvr_db_mixin, and extraroute_db.ExtraRoute_db_mixin. """ supported_extension_aliases = ["dvr", "router", "ext-gw-mode", "extraroute", "l3_agent_scheduler", "l3-ha", "security-group"] @resource_registry.tracked_resources(router=l3_db.Router, floatingip=l3_db.FloatingIP, security_group=securitygroups_db.SecurityGroup) def __init__(self): self.aws_utils = AwsUtils() super(AwsRouterPlugin, self).__init__() l3_db.subscribe() def get_plugin_type(self): return constants.L3_ROUTER_NAT def get_plugin_description(self): """returns string description of the plugin.""" return ("AWS L3 Router Service Plugin for basic L3 forwarding" " between (L2) Neutron networks and access to external" " networks via a NAT gateway.") ########## FLOATING IP FEATURES ############### def create_floatingip(self, context, floatingip): try: response = self.aws_utils.allocate_elastic_ip() public_ip_allocated = response['PublicIp'] LOG.info("Created elastic IP %s" % public_ip_allocated) if 'floatingip' in floatingip: floatingip['floatingip']['floating_ip_address'] = public_ip_allocated if 'port_id' in floatingip['floatingip'] and floatingip['floatingip']['port_id'] is not None: # Associate to a Port port_id = floatingip['floatingip']['port_id'] self._associate_floatingip_to_port(context, public_ip_allocated, port_id) except Exception as e: LOG.error("Error in Creation/Allocating EIP") if public_ip_allocated: LOG.error("Deleting Elastic IP: %s" % public_ip_allocated) self.aws_utils.delete_elastic_ip(public_ip_allocated) raise e try: res = super(AwsRouterPlugin, self).create_floatingip( context, floatingip, initial_status=n_const.FLOATINGIP_STATUS_DOWN) except Exception as e: LOG.error("Error when adding floating ip in openstack. Deleting Elastic IP: %s" % public_ip_allocated) self.aws_utils.delete_elastic_ip(public_ip_allocated) raise e return res def _associate_floatingip_to_port(self, context, floating_ip_address, port_id): port = self._core_plugin.get_port(context, port_id) ec2_id = None fixed_ip_address = None # TODO: Assuming that there is only one fixed IP if len(port['fixed_ips']) > 0: fixed_ip = port['fixed_ips'][0] if 'ip_address' in fixed_ip: fixed_ip_address = fixed_ip['ip_address'] search_opts = {'ip': fixed_ip_address, 'tenant_id': context.tenant_id} server_list = self.aws_utils.get_nova_client().servers.list(search_opts=search_opts) if len(server_list) > 0: server = server_list[0] if 'ec2_id' in server.metadata: ec2_id = server.metadata['ec2_id'] if floating_ip_address is not None and ec2_id is not None: self.aws_utils.associate_elastic_ip_to_ec2_instance(floating_ip_address, ec2_id) LOG.info("EC2 ID found for IP %s : %s" % (fixed_ip_address, ec2_id)) else: LOG.warning("EC2 ID not found to associate the floating IP") raise exceptions.AwsException(error_code="No Server Found", message="No server found with the Required IP") def update_floatingip(self, context, id, floatingip): floating_ip_dict = super(AwsRouterPlugin, self).get_floatingip(context, id) if 'floatingip' in floatingip and 'port_id' in floatingip['floatingip']: port_id = floatingip['floatingip']['port_id'] if port_id is not None: # Associate Floating IP LOG.info("Associating elastic IP %s with port %s" % (floating_ip_dict['floating_ip_address'], port_id)) self._associate_floatingip_to_port(context, floating_ip_dict['floating_ip_address'], port_id) else: try: # Port Disassociate self.aws_utils.disassociate_elastic_ip_from_ec2_instance(floating_ip_dict['floating_ip_address']) except exceptions.AwsException as e: if 'Association ID not found' in e.msg: # Since its already disassociated on EC2, we continue and remove the association here. LOG.warn("Association for Elastic IP not found. Probable out of band change on EC2.") elif 'InvalidAddress.NotFound' in e.msg: LOG.warn("Elastic IP cannot be found in EC2. Probably removed out of band on EC2.") else: raise e return super(AwsRouterPlugin, self).update_floatingip(context, id, floatingip) def delete_floatingip(self, context, id): floating_ip = super(AwsRouterPlugin, self).get_floatingip(context, id) floating_ip_address = floating_ip['floating_ip_address'] LOG.info("Deleting elastic IP %s" % floating_ip_address) try: self.aws_utils.delete_elastic_ip(floating_ip_address) except exceptions.AwsException as e: if 'InvalidAddress.NotFound' in e.msg: LOG.warn("Elastic IP not found on AWS. Cleaning up neutron db") else: raise e return super(AwsRouterPlugin, self).delete_floatingip(context, id) ##### ROUTERS ##### def create_router(self, context, router): try: router_name = router['router']['name'] internet_gw_res = self.aws_utils.create_internet_gateway_resource() ret_obj = super(AwsRouterPlugin, self).create_router(context, router) internet_gw_res.create_tags(Tags=[ {'Key': 'Name', 'Value': router_name}, {'Key': 'openstack_router_id', 'Value': ret_obj['id']} ]) LOG.info("Created AWS router %s with openstack id %s" % (router_name, ret_obj['id'])) return ret_obj except Exception as e: LOG.error("Error while creating router %s" % e) raise e def delete_router(self, context, id): try: LOG.info("Deleting router %s" % id) self.aws_utils.detach_internet_gateway_by_router_id(id) self.aws_utils.delete_internet_gateway_by_router_id(id) except Exception as e: LOG.error("Error in Deleting Router: %s " % e) raise e return super(AwsRouterPlugin, self).delete_router(context, id) def update_router(self, context, id, router): ## get internet gateway resource by openstack router id and update the tags try: if 'router' in router and 'name' in router['router']: router_name = router['router']['name'] tags_list = [ {'Key': 'Name', 'Value': router_name}, {'Key': 'openstack_router_id', 'Value': id} ] LOG.info("Updated router %s" % id) self.aws_utils.create_tags_internet_gw_from_router_id(id, tags_list) except Exception as e: LOG.error("Error in Updating Router: %s " % e) raise e return super(AwsRouterPlugin, self).update_router(context, id, router) ###### ROUTER INTERFACE ###### def add_router_interface(self, context, router_id, interface_info): subnet_id = interface_info['subnet_id'] subnet_obj = self._core_plugin.get_subnet(context, subnet_id) LOG.info("Adding subnet %s to router %s" % (subnet_id, router_id)) neutron_network_id = subnet_obj['network_id'] try: # Get Internet Gateway ID ig_id = self.aws_utils.get_internet_gw_from_router_id(router_id) # Get VPC ID vpc_id = self.aws_utils.get_vpc_from_neutron_network_id(neutron_network_id) self.aws_utils.attach_internet_gateway(ig_id, vpc_id) # Search for a Route table tagged with Router-id route_tables = self.aws_utils.get_route_table_by_router_id(router_id) if len(route_tables) == 0: # If not tagged, Fetch all the Route Tables Select one and tag it route_tables = self.aws_utils.describe_route_tables_by_vpc_id(vpc_id) if len(route_tables) > 0: route_table = route_tables[0] route_table_res = self.aws_utils._get_ec2_resource().RouteTable(route_table['RouteTableId']) route_table_res.create_tags(Tags=[ {'Key': 'openstack_router_id', 'Value': router_id} ]) if len(route_tables) > 0: route_table = route_tables[0] self.aws_utils.create_default_route_to_ig(route_table['RouteTableId'], ig_id, ignore_errors=True) except Exception as e: LOG.error("Error in Creating Interface: %s " % e) raise e return super(AwsRouterPlugin, self).add_router_interface(context, router_id, interface_info) def remove_router_interface(self, context, router_id, interface_info): LOG.info("Deleting port %s from router %s" % (interface_info['port_id'], router_id)) self.aws_utils.detach_internet_gateway_by_router_id(router_id) route_tables = self.aws_utils.get_route_table_by_router_id(router_id) if route_tables: route_table_id = route_tables[0]['RouteTableId'] self.aws_utils.delete_default_route_to_ig(route_table_id) return super(AwsRouterPlugin, self).remove_router_interface(context, router_id, interface_info)