SEBA-108 acquire_service_instance and validate_links methods
Change-Id: Ie63de0fd7beb0aa50d4f482f7c1c9000ebb2cb34
diff --git a/xos/synchronizer/models/convenience/fabric_crossconnect_service.py b/xos/synchronizer/models/convenience/fabric_crossconnect_service.py
new file mode 100644
index 0000000..c2bb8bf
--- /dev/null
+++ b/xos/synchronizer/models/convenience/fabric_crossconnect_service.py
@@ -0,0 +1,111 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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 xosapi.orm import ORMWrapper, register_convenience_wrapper
+from xosapi.convenience.service import ORMWrapperService
+
+class ORMWrapperFabricCrossconnectService(ORMWrapperService):
+
+ """ Calling convention. Assume the subscribing service does approximately (needs some checks to see
+ if the methods exist before calling them) the following in its model_policy:
+
+ if not eastbound_service.validate_links(self):
+ eastbound_service.acquire_service_instance(self)
+ """
+
+ def acquire_service_instance(self, subscriber_service_instance):
+ """ Given a subscriber_service_instance:
+ 1) If there is an eligible provider_service_instance that can be used, then link to it
+ 2) Otherwise, create a new provider_service_instance and link to it.
+ """
+ (s_tag, switch_datapath_id, source_port) = self._get_west_fields(subscriber_service_instance)
+
+ FabricCrossconnectServiceInstance = self.stub.FabricCrossconnectServiceInstance
+ ServiceInstanceLink = self.stub.ServiceInstanceLink
+
+ candidates = FabricCrossconnectServiceInstance.objects.filter(owner_id=self.id,
+ s_tag=s_tag,
+ switch_datapath_id=switch_datapath_id,
+ source_port=source_port)
+
+ if candidates:
+ provider_service_instance = candidates[0]
+ else:
+ provider_service_instance = FabricCrossconnectServiceInstance(owner=self,
+ s_tag=s_tag,
+ switch_datapath_id=switch_datapath_id,
+ source_port=source_port)
+ provider_service_instance.save()
+
+ # NOTE: Lack-of-atomicity vulnerability -- provider_service_instance could be deleted before we created the
+ # link.
+
+ link = ServiceInstanceLink(provider_service_instance=provider_service_instance,
+ subscriber_service_instance=subscriber_service_instance)
+ link.save()
+
+ return provider_service_instance
+
+ def validate_links(self, subscriber_service_instance):
+ """ Validate existing links between the provider and subscriber service instances. If a valid link exists,
+ then return it. Return [] otherwise.
+
+ As a side-effect, delete any invalid links.
+ """
+
+ # Short-cut -- if there are no subscriber links then we can skip getting all the properties.
+ if not subscriber_service_instance.subscribed_links.exists():
+ return None
+
+ (s_tag, switch_datapath_id, source_port) = self._get_west_fields(subscriber_service_instance)
+
+ matched = []
+ for link in subscriber_service_instance.subscribed_links.all():
+ if link.provider_service_instance.owner.id == self.id:
+ fcsi = link.provider_service_instance.leaf_model
+ if (fcsi.s_tag == s_tag) and (fcsi.switch_datapath_id == switch_datapath_id) and \
+ (fcsi.source_port == source_port):
+ matched.append(fcsi)
+ else:
+ link.delete()
+ return matched
+
+ def _get_west_fields(self, subscriber_si):
+ """ _get_west_fields()
+
+ Helper function to inspect westbound service instance for fields that will be used inside of
+ FabricCrossconnectServiceInstance.
+ """
+
+ s_tag = subscriber_si.get_westbound_service_instance_properties("s_tag", include_self=True)
+ switch_datapath_id = subscriber_si.get_westbound_service_instance_properties("switch_datapath_id", include_self=True)
+ source_port = subscriber_si.get_westbound_service_instance_properties("switch_port", include_self=True)
+
+ if (s_tag is None):
+ raise Exception("Subscriber ServiceInstance %s s-tag is None" % subscriber_si.id)
+
+ if (not switch_datapath_id):
+ raise Exception("Subscriber ServiceInstance %s switch_datapath_id is unset" % subscriber_si.id)
+
+ if (source_port is None):
+ raise Exception("Subscriber ServiceInstance %s switch_port is None" % subscriber_si.id)
+
+ s_tag = int(s_tag)
+ source_port = int(source_port)
+
+ return (s_tag, switch_datapath_id, source_port)
+
+register_convenience_wrapper("FabricCrossconnectService", ORMWrapperFabricCrossconnectService)
diff --git a/xos/synchronizer/models/fabric-crossconnect.xproto b/xos/synchronizer/models/fabric-crossconnect.xproto
index d0d78e4..bbcfa66 100644
--- a/xos/synchronizer/models/fabric-crossconnect.xproto
+++ b/xos/synchronizer/models/fabric-crossconnect.xproto
@@ -11,9 +11,9 @@
option verbose_name = "Fabric Crossconnect Service Instance";
option owner_class_name="FabricCrossconnectService";
- optional int32 s_tag = 1 [help_text = "s-tag"];
- optional string switch_datapath_id = 2 [help_text = "switch datapath id"];
- optional int32 source_port = 3 [help_text = "source port of fabric crossconnect"];
+ required int32 s_tag = 1 [help_text = "s-tag"];
+ required string switch_datapath_id = 2 [help_text = "switch datapath id"];
+ required int32 source_port = 3 [help_text = "source port of fabric crossconnect"];
}
message BNGPortMapping (XOSBase) {