""" 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.aws_utils import AwsUtils from neutron.common import constants as n_const from neutron.common import exceptions 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.db import securitygroups_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 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(fixed_ip): 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)