gluon/gluon/shim/backends/opendaylight/odl_net_l3vpn.py

352 lines
13 KiB
Python

# Copyright (c) 2016 Ericsson, Inc.
# All Rights Reserved
#
# 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 gluon.shim.base import HandlerBase
from gluon.shim import utils
from odlc import RestConfClient
from oslo_log import log as logging
import uuid as UUID
LOG = logging.getLogger(__name__)
class OdlNetL3VPN(HandlerBase):
def __init__(self):
self.odlclient = RestConfClient()
self.l3vpn_network_id = None
self.l3vpn_subnets = dict()
self.l3vpn_ports = dict()
def bind_port(self, uuid, model, changes):
"""Called to bind port to VM.
:param uuid: UUID of Port
:param model: Model object
:returns: dict of vif parameters (vif_type, vif_details)
"""
LOG.info("bind_port: %s" % uuid)
LOG.info(changes)
port = model.ports.get(uuid, None)
if not port:
LOG.error("Cannot find port")
return dict()
retval = {'vif_type': 'ovs',
'vif_details': {'port_filter': False,
'bridge_name': 'br-int'}}
return retval
def unbind_port(self, uuid, model, changes):
"""Called to unbind port from VM.
:param uuid: UUID of Port
:param model: Model object
:returns: None
"""
LOG.info("unbind_port: %s" % uuid)
LOG.info(changes)
def modify_port(self, uuid, model, changes):
"""Called when attributes change on a bound port.
:param uuid: UUID of Port
:param model: Model object
:param changes: dictionary of changed attributes
:returns: None
"""
LOG.info("modify_port: %s" % uuid)
LOG.info(changes)
def delete_port(self, uuid, model):
"""Called when a bound port is deleted
:param uuid: UUID of Port
:param model: Model object
:returns: None
"""
pass
def modify_interface(self, uuid, model, changes):
"""Called when attributes on an interface
:param uuid: UUID of Interface
:param model: Model Object
:param changes: dictionary of changed attributes
:returns: None
"""
LOG.info("modify_interface: %s" % uuid)
LOG.info(changes)
LOG.info("Updating IETF interface")
parent_interface = 'tap%s' % uuid[:11]
self.odlclient.update_ietf_interface(uuid, parent_interface)
def delete_interface(self, uuid, model, changes):
"""Called when an interface is deleted
:param uuid: UUID of Interface
:param model: Model Object
:param changes: dictionary of changed attributes
:returns: None
"""
LOG.info("delete_interface: %s" % uuid)
LOG.info("Deleting IETF Interface")
self.odlclient.delete_ietf_interface(uuid)
def modify_service(self, uuid, model, changes):
"""Called when attributes change on a bound port's service
:param uuid: UUID of Service
:param model: Model Object
:param changes: dictionary of changed attributes
:returns: None
"""
LOG.info("modify_service: %s" % uuid)
LOG.info(changes)
LOG.info("Creating or updating VPN instance")
vpn_instance = model.vpn_instances.get(uuid)
if vpn_instance:
self._create_or_update_service(vpn_instance)
else:
LOG.error("VPN instance %s not found" % uuid)
def delete_service(self, uuid, model, changes):
"""Called when a service associated with a bound port is deleted
:param uuid: UUID of Service
:param model: Model Object
:param changes: dictionary of changed attributes
:returns: None
"""
LOG.info("Deleting VPN Instance")
self.odlclient.delete_l3_vpn_instance(uuid)
def modify_service_binding(self, uuid, model, prev_binding):
"""Called when a service is associated with a bound port.
:param uuid: UUID of Port
:param model: Model Object
:param prev_binding: dictionary of previous binding
:returns: None
"""
LOG.info("modify_service_binding: %s" % uuid)
LOG.info(prev_binding)
vpn_binding = model.vpnbindings[uuid]
vpn_instance_id = vpn_binding.service_id
vpn_instance = model.vpn_instances[vpn_instance_id]
interface = model.interfaces.get(uuid)
port = model.ports.get(uuid)
subnet_name = self._create_l3vpn_port(
model,
port,
interface,
vpn_binding)
adjacency = [{"ip_address": vpn_binding.ipaddress,
"mac_address": port.mac_address,
"primary-adjacency": "true",
"subnet_id": self.l3vpn_subnets[subnet_name]
}]
LOG.info("Creating or updating VPN Interface")
self.odlclient.update_vpn_interface(
interface.id,
vpn_instance,
adjacency)
def delete_service_binding(self, model, prev_binding):
"""Called when a service is disassociated with a bound port.
:param model: Model Object
:param prev_binding: dictionary of previous binding
:returns: None
"""
LOG.info("Deleting L3VPN port")
self._delete_l3vpn_port(model, prev_binding)
LOG.info("Deleting VPN Interface")
self.odlclient.delete_vpn_interface(prev_binding.interface_id)
def modify_subport_parent(self, uuid, model, prev_parent,
prev_parent_type):
"""Called when a subport's parent relationship changes.
:param uuid: UUID of Subport
:param model: Model object
:param prev_parent: UUID of previous parent
:param prev_parent_type: name of previous parent (Port or Subport)
:returns: None
"""
pass
def _create_or_update_service(self, vpn_instance):
"""Called when creating or updating a VPN instance
:param vpn_instance: the VPN instance to create or update
:return: None
"""
ipv4_vpnTargets = {'vrfRTType': 'both',
'vrfRTValue': vpn_instance.get("ipv4_family")}
self.odlclient.update_l3_vpn_instance(
vpn_instance.id,
vpn_instance.get("route_distinguishers"),
ipv4_vpnTargets)
def _create_l3vpn_port(self, model, port, interface, vpn_binding):
# create network for port if needed
self._create_l3vpn_network(model)
# create subnet for port if needed
subnet_name = self._create_l3vpn_subnet(
model,
port,
interface,
vpn_binding)
# create a L3VPN port if needed
if interface.id not in self.l3vpn_ports:
p = self.odlclient.get_l3vpn_port(interface.id)
if not p:
# port does not exist yet -> create it
self.odlclient.update_l3vpn_port(
self.l3vpn_network_id,
self.l3vpn_subnets[subnet_name],
port,
vpn_binding)
# add to port cache
self.l3vpn_ports[interface.id] = interface
return subnet_name
def _create_l3vpn_network(self, model):
"""create a L3VPN network if needed """
if self.l3vpn_network_id is None:
networks = self.odlclient.get_l3vpn_networks()
for network in networks['networks']['network']:
if network['name'] == 'GluonL3VPNNetwork':
LOG.info('Caching UUID %s of Gluon L3VPN network %s' %
(network['uuid'], network['name']))
self.l3vpn_network_id = network['uuid']
# no existing L3VPN network found -> create one
if self.l3vpn_network_id is None:
id = UUID.uuid4()
# TODO(egeokun): support multi-tenancy
tenant_id = model.ports[model.ports.keys()[0]]['tenant_id']
self.odlclient.update_l3vpn_network(id, UUID.UUID(tenant_id))
self.l3vpn_network_id = id
def _create_l3vpn_subnet(self, model, port, interface, vpn_binding):
"""create a L3VPN subnet if needed """
ip_address = vpn_binding.ipaddress
prefix = int(vpn_binding.subnet_prefix)
network = utils.compute_network_addr(ip_address, prefix)
subnet_name = "GluonL3VPNSubnet_" + network
if subnet_name not in self.l3vpn_subnets:
# try to refresh cache
subnets = self.odlclient.get_l3vpn_subnets()
for subnet in subnets['subnets']['subnet']:
if subnet['name'] == subnet_name:
LOG.info('Caching Gluon L3VPN subnet %s (%s)' %
(subnet_name, subnet['uuid']))
self.l3vpn_subnets[subnet_name] = subnet['uuid']
# no subnet exists yet -> create one
if subnet_name not in self.l3vpn_subnets:
id = UUID.uuid4()
self.odlclient.update_l3vpn_subnet(
id,
self.l3vpn_network_id,
UUID.UUID(port.tenant_id),
network,
prefix)
self.l3vpn_subnets[subnet_name] = id
return subnet_name
def _delete_l3vpn_port(self, model, vpn_binding):
uuid = vpn_binding.interface_id
# clean up L3VPN port
if uuid not in self.l3vpn_ports:
# check if port exists in ODL
l3vpn_ports = self.odlclient.get_l3vpn_ports()
for l3vpn_port in l3vpn_ports['ports']['port']:
if l3vpn_port['uuid'] == uuid:
LOG.info('Found L3VPN port %s on ODL. Deleting.' % uuid)
self.odlclient.delete_l3vpn_port(uuid)
else:
del self.l3vpn_ports[uuid]
LOG.info('Deleting L3VPN port %s.' % uuid)
self.odlclient.delete_l3vpn_port(uuid)
subnet = utils.compute_network_addr(
vpn_binding.ipaddress,
int(vpn_binding.subnet_prefix))
# clean up L3VPN subnet
found_another_port = False
for k in model.vpnbindings:
this_vpn_binding = model.vpnbindings[k]
sn = utils.compute_network_addr(
this_vpn_binding.ipaddress,
this_vpn_binding.subnet_prefix)
if subnet == sn:
found_another_port = True
LOG.info("Found another port on the subnet. Keeping subnet.")
break
if not found_another_port:
subnet_name = "GluonL3VPNSubnet_" + subnet
if subnet_name not in self.l3vpn_subnets:
subnets = self.odlclient.get_l3vpn_subnets()
for snet in subnets['subnets']['subnet']:
if snet.name == subnet_name:
LOG.info('Deleting Gluon L3VPN subnet %s (%s)' %
(subnet_name, snet.uuid))
self.odlclient.delete_l3vpn_subnet(snet.uuid)
del self.l3vpn_subnets[snet.uuid]
break
else:
LOG.info('Found L3VPN subnet %s (%s) in cache. Deleting.' %
(subnet_name, self.l3vpn_subnets[subnet_name]))
self.odlclient.delete_l3vpn_subnet(
self.l3vpn_subnets[subnet_name])
del self.l3vpn_subnets[subnet_name]
# clean up L3VPN network
if not self.l3vpn_ports:
if self.l3vpn_network_id is None:
l3vpn_networks = self.odlclient.get_l3vpn_networks()
for l3vpn_network in l3vpn_networks['networks']['network']:
if l3vpn_network['name'] == 'GluonL3VPNNetwork':
LOG.info("Deleting L3VPN network %s."
% self.l3vpn_network_id)
self.odlclient.delete_l3vpn_network(
l3vpn_network['uuid'])
else:
LOG.info("L3VPN network %s found in cache. Deleting."
% self.l3vpn_network_id)
self.odlclient.delete_l3vpn_network(self.l3vpn_network_id)
self.l3vpn_network_id = None
else:
LOG.info('Still L3VPN ports present. '
'Not deleting L3VPN network: %s' % self.l3vpn_ports)