Updating the ODL shim layer to port binding model

This patch modifies the ODl shim layer to comply with the revised
port and interface binding model in Gluon.

Change-Id: Ib4c7800203165474b4b72ac40ab5ef363d831d0c
Signed-off-by: Georg Kunz <georg.kunz@ericsson.com>
This commit is contained in:
Georg Kunz 2017-02-13 17:33:03 +01:00
parent ade1ed78e2
commit 617efa3616
4 changed files with 226 additions and 129 deletions

View File

@ -50,7 +50,7 @@ objects:
attributes:
service_id: # Override from base object for specific Service type
type: VpnService
interface_id: # Override from base object for specific Inteface type
interface_id: # Override from base object for specific Interface type
type: Interface
ipaddress:
type: string

View File

@ -45,10 +45,13 @@ class ApiNetL3VPN(ApiModelBase):
statuses = shim_data.client.read(etcd_path)
if statuses:
for status in statuses.children:
attributes = json.loads(status.value)
self.handle_object_change(obj_name,
os.path.basename(status.key),
attributes, shim_data)
if status.value is not None:
attributes = json.loads(status.value)
self.handle_object_change(
obj_name,
os.path.basename(status.key),
attributes,
shim_data)
except Exception as e:
LOG.error("reading %s keys failed: %s" % (obj_name, str(e)))
pass

View File

@ -27,6 +27,7 @@ class OdlNetL3VPN(HandlerBase):
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.
@ -43,36 +44,6 @@ class OdlNetL3VPN(HandlerBase):
LOG.error("Cannot find port")
return dict()
# binding tap device on compute host by creating an IETF interface
LOG.info("Updating IETF interface")
parent_interface = 'tap%s' % uuid[:11]
self.odlclient.update_ietf_interface(uuid, parent_interface)
# check if a valid service binding exists already in the model
# if so, create or update it on the SDN controller
service_binding = model.vpnbindings.get(uuid, None)
if not service_binding:
LOG.info("Port not bound to a service yet.")
else:
vpn_instance = model.vpn_instances.get(
service_binding["service_id"],
None)
if not vpn_instance:
LOG.warn("VPN instance not defined yet.")
else:
LOG.info("port: %s" % port)
LOG.info("service: %s" % vpn_instance)
LOG.info("Creating or updating VPN instance")
self._create_or_update_service(vpn_instance)
LOG.info("Creating or updating VPN Interface")
adjacency = [{"ip_address": port.ipaddress,
"mac_address": port.mac_address}]
self.odlclient.update_vpn_interface(
port.id,
vpn_instance.id,
adjacency)
retval = {'vif_type': 'ovs',
'vif_details': {'port_filter': False,
'bridge_name': 'br-int'}}
@ -88,10 +59,6 @@ class OdlNetL3VPN(HandlerBase):
LOG.info("unbind_port: %s" % uuid)
LOG.info(changes)
# we keep the service binding untouched for now
LOG.info("Deleting IETF Interface")
self.odlclient.delete_ietf_interface(uuid)
def modify_port(self, uuid, model, changes):
"""Called when attributes change on a bound port.
@ -103,10 +70,6 @@ class OdlNetL3VPN(HandlerBase):
LOG.info("modify_port: %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_port(self, uuid, model, port):
"""Called when a bound port is deleted
@ -114,8 +77,7 @@ class OdlNetL3VPN(HandlerBase):
:param model: Model object
:returns: None
"""
LOG.info("Deleting IETF Interface")
self.odlclient.delete_ietf_interface(uuid)
pass
def modify_interface(self, uuid, model, changes):
"""Called when attributes on an interface
@ -128,6 +90,10 @@ class OdlNetL3VPN(HandlerBase):
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
@ -137,6 +103,8 @@ class OdlNetL3VPN(HandlerBase):
: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
@ -149,23 +117,6 @@ class OdlNetL3VPN(HandlerBase):
LOG.info("modify_service: %s" % uuid)
LOG.info(changes)
# create a L3VPN network if none exists yet
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
LOG.info("Creating or updating VPN instance")
vpn_instance = model.vpn_instances.get(uuid)
if vpn_instance:
@ -181,10 +132,6 @@ class OdlNetL3VPN(HandlerBase):
:param changes: dictionary of changed attributes
:returns: None
"""
LOG.info("Deleting L3VPN network")
self.odlclient.delete_l3vpn_network(self.l3vpn_network_id)
self.l3vpn_network_id = None
LOG.info("Deleting VPN Instance")
self.odlclient.delete_l3_vpn_instance(uuid)
@ -199,43 +146,29 @@ class OdlNetL3VPN(HandlerBase):
LOG.info("modify_service_binding: %s" % uuid)
LOG.info(prev_binding)
vpn_instance = model.vpnbindings[uuid].vpn_instance
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)
ip_address = port.ipaddress
prefix = int(port.subnet_prefix)
network = utils.compute_network_addr(ip_address, prefix)
network_name = "GluonL3VPNSubnet_" + network
subnet_name = self._create_l3vpn_port(
model,
port,
interface,
vpn_binding)
# if no subnet for this port exists
if network_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'] == network_name:
LOG.info('Caching Gluon L3VPN subnet %s (%s)' %
(network_name, subnet['uuid']))
self.l3vpn_subnets[network_name] = subnet['uuid']
# no subnet exists yet -> create one
if network_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[network_name] = id
adjacency = [{"ip_address": port.ipaddress,
adjacency = [{"ip_address": vpn_binding.ipaddress,
"mac_address": port.mac_address,
"primary-adjacency": "true",
"subnet_id": self.l3vpn_subnets[network_name]
"subnet_id": self.l3vpn_subnets[subnet_name]
}]
LOG.info("Creating or updating VPN Interface")
self.odlclient.update_vpn_interface(port.id, vpn_instance, adjacency)
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.
@ -244,39 +177,11 @@ class OdlNetL3VPN(HandlerBase):
:param prev_binding: dictionary of previous binding
:returns: None
"""
port = model.ports.get(prev_binding.id)
subnet = utils.compute_network_addr(port.ipaddress, port.subnet_prefix)
found_another_port = False
for k in model.vpnbindings:
vpnport = model.vpnbindings[k]
p = model.ports.get(vpnport.id)
sn = utils.compute_network_addr(p.ipaddress, p.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]
LOG.info("Deleting L3VPN port")
self._delete_l3vpn_port(model, prev_binding)
LOG.info("Deleting VPN Interface")
self.odlclient.delete_vpn_interface(prev_binding.id)
self.odlclient.delete_vpn_interface(prev_binding.interface_id)
def modify_subport_parent(self, uuid, model, prev_parent,
prev_parent_type):
@ -302,3 +207,145 @@ class OdlNetL3VPN(HandlerBase):
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)

View File

@ -16,6 +16,7 @@ from gluon.shim import utils
import json
from oslo_config import cfg
import requests
import uuid as UUID
try:
from neutron.openstack.common import jsonutils
except ImportError:
@ -134,6 +135,51 @@ class RestConfClient(ODL_Client):
output = self.sendjson('get', urlpath)
return output
def get_l3vpn_ports(self):
return self.get('neutron:neutron/ports')
def get_l3vpn_port(self, uuid):
return self.get('neutron:neutron/ports/port/' + uuid)
def update_l3vpn_port(self, network_id, subnet_id, port, vpn_binding):
l3vpn_port = \
{
"uuid": port.id,
"device-owner": "compute:None",
"name": "",
"fixed-ips": [
{
"subnet-id": subnet_id,
"ip-address": vpn_binding.ipaddress
}
],
"network-id": network_id,
"admin-state-up": "true",
"neutron-binding:vnic-type": "normal",
"neutron-binding:host-id": port.host_id,
"neutron-binding:vif-details": [
{
"details-key": "port_filter",
"value": "true"
}
],
"neutron-binding:vif-type": "ovs",
"tenant-id": UUID.UUID(port.tenant_id),
"mac-address": port.mac_address,
"neutron-portsecurity:port-security-enabled": "false"
}
# "device-id": "c6ba1b66-6149-4b7a-ad20-05072058ea3b",
# "security-groups": [
# "e08d477a-1b78-47cd-b502-591e3f3a6213"
# ],
self._update(l3vpn_port,
'neutron:neutron/ports',
'uuid',
'port')
def delete_l3vpn_port(self, uuid):
self.delete('neutron:neutron/ports/port', id=uuid)
def get_l3vpn_networks(self):
return self.get('neutron:neutron/networks')
@ -182,7 +228,8 @@ class RestConfClient(ODL_Client):
'cidr': network + "/" + str(prefix),
'enable-dhcp': 'true',
'tenant-id': tenant_id,
'allocation-pools': allocation_pool
'allocation-pools': allocation_pool,
'dns-nameservers': ['8.8.8.8']
}
self._update(subnet,
'neutron:neutron/subnets/',
@ -229,9 +276,9 @@ class RestConfClient(ODL_Client):
def get_vpn_interfaces(self):
return self.get('l3vpn:vpn-interfaces')
def update_vpn_interface(self, name, vpn_instance_name, adjacency):
def update_vpn_interface(self, name, vpn_instance, adjacency):
vpn_interface = {'name': name,
'vpn-instance-name': vpn_instance_name,
'vpn-instance-name': vpn_instance.id,
"odl-l3vpn:adjacency": adjacency}
self._update(vpn_interface,
'l3vpn:vpn-interfaces',