285 lines
13 KiB
Python
285 lines
13 KiB
Python
# Copyright (c) 2016 Nokia, 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.
|
|
|
|
import json
|
|
import os
|
|
|
|
import etcd
|
|
from oslo_log import log as logging
|
|
|
|
from gluon.shim.base import ApiModelBase
|
|
from gluon.shim import model as Model
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class ApiNetL3VPN(ApiModelBase):
|
|
|
|
def __init__(self):
|
|
super(self.__class__, self).__init__("net-l3vpn")
|
|
self.resync_mode = False
|
|
self.model.vpn_instances = dict()
|
|
self.model.vpnbindings = dict()
|
|
self.model.vpn_afconfigs = dict()
|
|
|
|
def load_model(self, shim_data):
|
|
self.resync_mode = True
|
|
objects = ["Port", "Interface", "VpnService",
|
|
"VpnAfConfig", "VpnBinding"]
|
|
for obj_name in objects:
|
|
etcd_path = "{0:s}/{1:s}/{2:s}".format("proton", self.name,
|
|
obj_name)
|
|
try:
|
|
statuses = shim_data.client.read(etcd_path)
|
|
if statuses:
|
|
for status in statuses.children:
|
|
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
|
|
self.resync_mode = False
|
|
|
|
def bind_attributes_changed(self, attributes):
|
|
return "host_id" in attributes and "device_id" in attributes
|
|
|
|
def is_bind_request(self, attributes):
|
|
host_id = attributes.get("host_id", None)
|
|
if host_id == "":
|
|
host_id = None
|
|
device_id = attributes.get("device_id", None)
|
|
if device_id == "":
|
|
device_id = None
|
|
return host_id is not None and device_id is not None
|
|
|
|
def get_etcd_bound_data(self, shim_data, key):
|
|
etcd_key = "{0:s}/{1:s}/{2:s}/{3:s}".format("controller", self.name,
|
|
"Port", key)
|
|
try:
|
|
vif_dict = json.loads(shim_data.client.get(etcd_key).value)
|
|
if not vif_dict:
|
|
LOG.debug("Empty value read for: %s" % etcd_key)
|
|
return {}
|
|
else:
|
|
return vif_dict
|
|
except etcd.EtcdKeyNotFound:
|
|
return {}
|
|
|
|
def update_etcd_unbound(self, shim_data, key):
|
|
etcd_key = "{0:s}/{1:s}/{2:s}/{3:s}".format("controller", self.name,
|
|
"Port", key)
|
|
try:
|
|
data = {}
|
|
shim_data.client.write(etcd_key, json.dumps(data))
|
|
except Exception as e:
|
|
LOG.error("Update etcd to unbound failed: %s" % str(e))
|
|
|
|
def update_etcd_bound(self, shim_data, key, vif_dict):
|
|
vif_dict["controller"] = shim_data.name
|
|
etcd_key = "{0:s}/{1:s}/{2:s}/{3:s}".format("controller", self.name,
|
|
"Port", key)
|
|
try:
|
|
shim_data.client.write(etcd_key, json.dumps(vif_dict))
|
|
except Exception as e:
|
|
LOG.error("Update etcd to bound failed: %s" % str(e))
|
|
|
|
def handle_port_change(self, key, attributes, shim_data):
|
|
if key in self.model.ports:
|
|
changes = self.model.ports[key].update_attrs(attributes)
|
|
if self.bind_attributes_changed(changes.new):
|
|
if self.model.ports[key].get("__state") == "Bound":
|
|
if self.is_bind_request(changes.new): # already bound?
|
|
LOG.error("Bind request on bound port?")
|
|
else: # Unbind
|
|
self.backend.unbind_port(key, self.model, changes)
|
|
vif_dict = self.get_etcd_bound_data(shim_data, key)
|
|
for vif_key in vif_dict:
|
|
self.model.ports[key][vif_key] = ""
|
|
self.update_etcd_unbound(shim_data, key)
|
|
self.model.ports[key]["__state"] = "Unbound"
|
|
elif self.model.ports[key].get("__state") == "Unbound":
|
|
if self.is_bind_request(changes.new):
|
|
# if one of my hosts
|
|
if changes.new["host_id"] in shim_data.host_list or \
|
|
'*' in shim_data.host_list:
|
|
vif_dict = self.backend.bind_port(key,
|
|
self.model,
|
|
changes)
|
|
if len(vif_dict) > 0: # Bind success
|
|
self.model.ports[key].\
|
|
update_attrs(vif_dict)
|
|
self.model.ports[key]["__state"] = "Bound"
|
|
self.update_etcd_bound(shim_data, key,
|
|
vif_dict)
|
|
else:
|
|
LOG.info("Bind request rejected")
|
|
else:
|
|
# Bound by another controller
|
|
self.model.ports[key]["__state"] = "InUse"
|
|
LOG.info("Port %s bound by other controller" % key)
|
|
else:
|
|
pass
|
|
elif self.model.ports[key].get("__state") == "InUse":
|
|
if self.is_bind_request(changes.new): # already bound?
|
|
LOG.error("Bind request on InUse port: %s" % key)
|
|
else:
|
|
self.model.ports[key]["__state"] = "Unbound"
|
|
else:
|
|
if self.model.ports[key].get("__state") == "Bound":
|
|
self.backend.modify_port(key, self.model, changes)
|
|
else:
|
|
port = Model.Port(key, attributes)
|
|
self.model.ports[key] = port
|
|
self.backend.modify_port(key, self.model, {})
|
|
vif_dict = self.get_etcd_bound_data(shim_data, key)
|
|
if len(vif_dict) > 0: # Bound
|
|
if vif_dict.get("controller") == \
|
|
shim_data.name: # Bound by me
|
|
self.model.ports[key]["__state"] = "Bound"
|
|
else:
|
|
self.model.ports[key]["__state"] = "InUse"
|
|
|
|
def handle_interface_change(self, key, attributes, shim_data):
|
|
if key in self.model.interfaces:
|
|
changes = self.model.interfaces[key].update_attrs(attributes)
|
|
self.backend.modify_interface(key, self.model, changes)
|
|
else:
|
|
obj = Model.DataObj(key, attributes)
|
|
self.model.interfaces[key] = obj
|
|
self.backend.modify_interface(key, self.model, attributes)
|
|
|
|
def handle_vpn_instance_change(self, key, attributes, shim_data):
|
|
if key in self.model.vpn_instances:
|
|
changes = self.model.vpn_instances[key].update_attrs(attributes)
|
|
self.backend.modify_service(key, self.model, changes)
|
|
else:
|
|
obj = Model.DataObj(key, attributes)
|
|
self.model.vpn_instances[key] = obj
|
|
self.backend.modify_service(key, self.model, attributes)
|
|
|
|
def handle_vpn_binding_change(self, key, attributes, shim_data):
|
|
if key in self.model.vpnbindings:
|
|
prev_binding = \
|
|
{"interface_id": self.model.vpnbindings[key].id,
|
|
"service_id": self.model.vpnbindings[key].service_id}
|
|
self.model.vpnbindings[key].update_attrs(attributes)
|
|
self.backend.modify_service_binding(key, self.model, prev_binding)
|
|
else:
|
|
obj = Model.DataObj(key, attributes)
|
|
self.model.vpnbindings[key] = obj
|
|
self.backend.modify_service_binding(key, self.model, {})
|
|
|
|
def handle_vpnafconfig_change(self, key, attributes, shim_data):
|
|
if key in self.model.vpn_afconfigs:
|
|
self.model.vpn_afconfigs[key].update_attrs(attributes)
|
|
for vpn_instance in self.model.vpn_instances.itervalues():
|
|
changes = Model.ChangeData()
|
|
if vpn_instance["ipv4_family"].find(key) != -1:
|
|
changes.new["ipv4_family"] = vpn_instance["ipv4_family"]
|
|
if vpn_instance["ipv6_family"].find(key) != -1:
|
|
changes.new["ipv6_family"] = vpn_instance["ipv6_family"]
|
|
if len(changes.new) > 0:
|
|
port = None
|
|
for vpn_binding in self.model.vpnbindings.itervalues():
|
|
if vpn_binding["service_id"] == vpn_instance["id"]:
|
|
port = self.model.ports.get(
|
|
vpn_binding["interface_id"])
|
|
if port and port.get("__state") == "Bound":
|
|
self.backend.modify_service(vpn_instance["id"],
|
|
self.model, changes)
|
|
else:
|
|
obj = Model.DataObj(key, attributes)
|
|
self.model.vpn_afconfigs[key] = obj
|
|
|
|
def handle_object_change(self, object_type, key, attributes, shim_data):
|
|
if object_type == "Port":
|
|
self.handle_port_change(key, attributes, shim_data)
|
|
elif object_type == "Interface":
|
|
self.handle_interface_change(key, attributes, shim_data)
|
|
elif object_type == "VpnService":
|
|
self.handle_vpn_instance_change(key, attributes, shim_data)
|
|
elif object_type == "VpnBinding":
|
|
self.handle_vpn_binding_change(key, attributes, shim_data)
|
|
elif object_type == "VpnAfConfig":
|
|
self.handle_vpnafconfig_change(key, attributes, shim_data)
|
|
else:
|
|
LOG.error("Unknown object: %s" % object_type)
|
|
|
|
def handle_port_delete(self, key, shim_data):
|
|
if key in self.model.ports:
|
|
# deleted_obj = self.model.ports[key]
|
|
del self.model.ports[key]
|
|
self.backend.delete_port(key, self.model)
|
|
|
|
def handle_interface_delete(self, key, shim_data):
|
|
if key in self.model.interfaces:
|
|
deleted_obj = self.model.interfaces[key]
|
|
del self.model.interfaces[key]
|
|
self.backend.delete_interface(key, self.model, deleted_obj)
|
|
|
|
def handle_vpn_instance_delete(self, key, shim_data):
|
|
if key in self.model.vpn_instances:
|
|
deleted_obj = self.model.vpn_instances[key]
|
|
del self.model.vpn_instances[key]
|
|
self.backend.delete_service(key, self.model, deleted_obj)
|
|
|
|
def handle_vpn_binding_delete(self, key, shim_data):
|
|
if key in self.model.vpnbindings:
|
|
deleted_obj = self.model.vpnbindings[key]
|
|
del self.model.vpnbindings[key]
|
|
self.backend.delete_service_binding(self.model, deleted_obj)
|
|
|
|
def handle_vpnafconfig_delete(self, key, shim_data):
|
|
if key in self.model.vpn_afconfigs:
|
|
del self.model.vpn_afconfigs[key]
|
|
for vpn_instance in self.model.vpn_instances.itervalues():
|
|
changes = Model.ChangeData()
|
|
if vpn_instance["ipv4_family"].find(key) != -1:
|
|
l = vpn_instance["ipv4_family"].split(',')
|
|
l.remove(key)
|
|
changes.new["ipv4_family"] = ','.join(l)
|
|
if vpn_instance["ipv6_family"].find(key) != -1:
|
|
l = vpn_instance["ipv6_family"].split(',')
|
|
l.remove(key)
|
|
changes.new["ipv6_family"] = ','.join(l)
|
|
if len(changes.new) > 0:
|
|
port = None
|
|
for vpn_binding in self.model.vpnbindings.itervalues():
|
|
if vpn_binding["service_id"] == vpn_instance["id"]:
|
|
port = self.model.ports.get(
|
|
vpn_binding["interface_id"])
|
|
if port and port.get("__state") == "Bound":
|
|
self.backend.modify_service(vpn_instance["id"],
|
|
self.model, changes)
|
|
|
|
def handle_object_delete(self, object_type, key, shim_data):
|
|
if object_type == "Port":
|
|
self.handle_port_delete(key, shim_data)
|
|
elif object_type == "Interface":
|
|
self.handle_interface_delete(key, shim_data)
|
|
elif object_type == "VpnService":
|
|
self.handle_vpn_instance_delete(key, shim_data)
|
|
elif object_type == "VpnBinding":
|
|
self.handle_vpn_binding_delete(key, shim_data)
|
|
elif object_type == "VpnAfConfig":
|
|
self.handle_vpnafconfig_delete(key, shim_data)
|
|
else:
|
|
LOG.error("Unknown object: %s" % object_type)
|