246 lines
12 KiB
Python
246 lines
12 KiB
Python
# 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)
|