omni/neutron/neutron/services/l3_router/aws_router_plugin.py

299 lines
13 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.
"""
import neutron_lib
from neutron_lib import constants as const
from distutils.version import LooseVersion
from neutron.common.aws_utils import AwsException
from neutron.common.aws_utils import AwsUtils
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
LOG = logging.getLogger(__name__)
if LooseVersion(neutron_lib.__version__) < LooseVersion("1.0.0"):
router = l3_db.Router
floating_ip = l3_db.FloatingIP
plugin_type = constants.L3_ROUTER_NAT
service_plugin_class = service_base.ServicePluginBase
else:
from neutron.db.models import l3
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.services import base
router = l3.Router
floating_ip = l3.FloatingIP
plugin_type = plugin_constants.L3
service_plugin_class = base.ServicePluginBase
class AwsRouterPlugin(
service_plugin_class, 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=router, floatingip=floating_ip)
def __init__(self):
self.aws_utils = AwsUtils()
super(AwsRouterPlugin, self).__init__()
l3_db.subscribe()
def get_plugin_type(self):
return plugin_type
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):
public_ip_allocated = None
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=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 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 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 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)