[CORD-2912] Read westbound attribute in the chain
Change-Id: I7216e02564a468f60cdfacd6e64c5e92a0254f69
diff --git a/docs/dev/synchronizers.md b/docs/dev/synchronizers.md
index e0f3c67..b75e4c5 100644
--- a/docs/dev/synchronizers.md
+++ b/docs/dev/synchronizers.md
@@ -16,3 +16,127 @@
describes a set of [design guidelines](sync_arch.md) and the following one
presents the [implementation details](sync_impl.md).
+## Overview of the synchronizer framework APIs
+
+This section is intended to be a reference for the commonly used APIs exposed by the synchronizer framework.
+
+### Model Policies
+
+Model Policies can be seen as `post-save` hooks and they are generally defined in the `xos/synchronizer/model_policies` folder of your service.
+
+Model policies are generally used to dynamically create a service chain (when a ServiceInstance is created it will create a ServiceInstance of its east side Service)
+
+> Note that you'll need to add this folder in your synchronizer configuration file as
+>```yaml
+> model_policies_dir: "/opt/xos/synchronizers/<synchronizer_name>/model_policies"
+>```
+
+A model policy is a class that inherits from `Policy`
+
+```python
+from synchronizers.new_base.modelaccessor import MyServiceInstance, ServiceInstanceLink, model_accessor
+from synchronizers.new_base.policy import Policy
+
+class MyServiceInstancePolicy(Policy):
+ model_name = "MyServiceInstance"
+```
+
+and overrides one or more of the following methods:
+
+```python
+def handle_create(self, model):
+```
+
+```python
+def handle_update(self, model):
+```
+
+```python
+def handle_delete(self, model):
+```
+> where model is the instance of the model that has been created
+
+### Sync Steps
+
+Sync Steps are the actual piece of code that provide the mapping between your models and your backend.
+You will need to define a sync step for each model.
+
+> Note that you'll need to add this folder in your synchronizer configuration file as
+>```yaml
+> steps_dir: "/opt/xos/synchronizers/<synchronizer_name>/steps"
+>```
+
+A Sync Step is a class that inherits from `SyncStep`
+
+```python
+
+from synchronizers.new_base.SyncInstanceUsingAnsible import SyncStep
+from synchronizers.new_base.modelaccessor import MyModel
+
+from xosconfig import Config
+from multistructlog import create_logger
+
+log = create_logger(Config().get('logging'))
+
+class SyncMyModel(SyncStep):
+ provides = [MyModel]
+
+ observes = MyModel
+```
+
+and provides these methods:
+```python
+def sync_record(self, o):
+ log.info("sync'ing object", object=str(o), **o.tologdict())
+```
+
+```python
+def delete_record(self, o):
+ log.info("deleting object", object=str(o), **o.tologdict())
+```
+
+This methods will be invoked anytime there is change in the model passing as argument the changed models.
+After performing the required operations to sync the model state with the backend state the synchronizer
+framework will update the models with the operational informations needed.
+
+### Pull Steps
+
+Pull Steps can be used to observe the surrounding environment and update the data-model
+accordingly.
+
+> Note that you'll need to add this folder in your synchronizer configuration file as
+>```yaml
+> pull_steps_dir: "/opt/xos/synchronizers/<synchronizer_name>/pull_steps"
+>```
+
+A Sync Step is a class that inherits from `PullStep`
+
+```python
+from synchronizers.new_base.pullstep import PullStep
+from synchronizers.new_base.modelaccessor import OLTDevice
+
+from xosconfig import Config
+from multistructlog import create_logger
+
+log = create_logger(Config().get('logging'))
+
+from synchronizers.new_base.modelaccessor import MyModel
+
+class MyModelPullStep(PullStep):
+ def __init__(self):
+ super(MyModelPullStep, self).__init__(observed_model=OLTDevice)
+```
+
+and override the following method:
+
+```python
+def pull_records(self):
+ log.info("pulling MyModels")
+ # create an empty model
+ o = MyModel()
+ # code to fetch information
+ # populate the model
+ o.foo = 'bar'
+ o.no_sync = True # this is required to prevent the synchronizer to be invoked and start a loop
+ o.save()
+```
\ No newline at end of file
diff --git a/xos/xos_client/xosapi/convenience/onosapp.py b/xos/xos_client/xosapi/convenience/onosapp.py
index 331d518..187084c 100644
--- a/xos/xos_client/xosapi/convenience/onosapp.py
+++ b/xos/xos_client/xosapi/convenience/onosapp.py
@@ -16,9 +16,9 @@
import json
from xosapi.orm import ORMWrapper, register_convenience_wrapper
-from xosapi.convenience.tenant import ORMWrapperTenant
+from xosapi.convenience.serviceinstance import ORMWrapperServiceInstance
-class ORMWrapperONOSApp(ORMWrapperTenant):
+class ORMWrapperONOSApp(ORMWrapperServiceInstance):
pass
register_convenience_wrapper("ONOSApp", ORMWrapperONOSApp)
diff --git a/xos/xos_client/xosapi/convenience/serviceinstance.py b/xos/xos_client/xosapi/convenience/serviceinstance.py
new file mode 100644
index 0000000..5bc79d7
--- /dev/null
+++ b/xos/xos_client/xosapi/convenience/serviceinstance.py
@@ -0,0 +1,86 @@
+# 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
+
+import logging as log
+
+class ORMWrapperServiceInstance(ORMWrapper):
+
+ @property
+ def serviceinstanceattribute_dict(self):
+ attrs = {}
+ for attr in self.service_instance_attributes.all():
+ attrs[attr.name] = attr.value
+ return attrs
+
+ @property
+ def tenantattribute_dict(self):
+ log.warn("tenantattribute_dict method is deprecated")
+ return self.serviceinstanceattribute_dict
+
+ @property
+ def westbound_service_instances(self):
+ links = self.provided_links.all()
+ instances = []
+ for link in links:
+ instances.append(link.subscriber_service_instance.leaf_model)
+
+ return instances
+
+ @property
+ def eastbound_service_instances(self):
+ links = self.subscriber_links.all()
+ instances = []
+ for link in links:
+ instances.append(link.provider_service_instance.leaf_model)
+
+ return instances
+
+ def create_eastbound_instance(self):
+
+ # Already has a chain
+ if len(self.eastbound_service_instances) > 0 and not self.is_new:
+ log.debug("MODEL_POLICY: Subscriber %s is already part of a chain" % si.id)
+ return
+
+ # if it does not have a chain,
+ # Find links to the next element in the service chain
+ # and create one
+
+ links = self.owner.subscribed_dependencies.all()
+
+ for link in links:
+ si_class = link.provider_service.get_service_instance_class_name()
+ log.info(" %s creating %s" % (self.model_name, si_class))
+
+ eastbound_si_class = model_accessor.get_model_class(si_class)
+ eastbound_si = eastbound_si_class()
+ eastbound_si.owner_id = link.provider_service_id
+ eastbound_si.save()
+ link = ServiceInstanceLink(provider_service_instance=eastbound_si, subscriber_service_instance=si)
+ link.save()
+
+ def get_westbound_service_instance_properties(self, prop_name):
+ wi = self.westbound_service_instances
+
+ if len(wi) == 0:
+ raise Exception('ServiceInstance %s has no westbound service instances')
+
+ for i in wi:
+ if hasattr(i, prop_name):
+ return getattr(i, prop_name)
+ else:
+ return i.get_westbound_service_instance_properties(prop_name)
+
+register_convenience_wrapper("ServiceInstance", ORMWrapperServiceInstance)
\ No newline at end of file
diff --git a/xos/xos_client/xosapi/convenience/tenant.py b/xos/xos_client/xosapi/convenience/tenant.py
deleted file mode 100644
index 3b5ea28..0000000
--- a/xos/xos_client/xosapi/convenience/tenant.py
+++ /dev/null
@@ -1,35 +0,0 @@
-
-# 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.
-
-
-import json
-from xosapi.orm import ORMWrapper, register_convenience_wrapper
-
-class ORMWrapperServiceInstance(ORMWrapper):
- @property
- def serviceinstanceattribute_dict(self):
- attrs = {}
- for attr in self.service_instance_attributes.all():
- attrs[attr.name] = attr.value
- return attrs
-
- @property
- def tenantattribute_dict(self):
- return self.serviceinstanceattribute_dict
-
-class ORMWrapperTenant(ORMWrapperServiceInstance):
- pass
-
-register_convenience_wrapper("ServiceInstance", ORMWrapperServiceInstance)
diff --git a/xos/xos_client/xosapi/convenience/voltserviceinstance.py b/xos/xos_client/xosapi/convenience/voltserviceinstance.py
index 3168aed..8b69bf5 100644
--- a/xos/xos_client/xosapi/convenience/voltserviceinstance.py
+++ b/xos/xos_client/xosapi/convenience/voltserviceinstance.py
@@ -16,10 +16,13 @@
from xosapi.orm import ORMWrapper, register_convenience_wrapper
+import logging as log
+
class ORMWrapperVOLTServiceInstance(ORMWrapper):
@property
def vsg(self):
+ log.warning('VOLTServiceInstance.vsg is DEPRECATED, use get_westbound_service_instance_properties instead')
links = self.stub.ServiceInstanceLink.objects.filter(subscriber_service_instance_id = self.id)
for link in links:
# cast from ServiceInstance to VSGTenant
@@ -31,11 +34,13 @@
# DEPRECATED
@property
def vcpe(self):
- self.logger.warning('VOLTServiceInstance.vcpe is DEPRECATED, use VOLTServiceInstance.vsg instead')
+ log.warning('VOLTServiceInstance.vcpe is DEPRECATED, use VOLTServiceInstance.vsg instead')
return self.vsg
@property
def subscriber(self):
+ log.warning(
+ 'VOLTServiceInstance.subscriber is DEPRECATED, use get_westbound_service_instance_properties instead')
# NOTE this assume that each VOLT has just 1 subscriber, is that right?
links = self.stub.ServiceInstanceLink.objects.filter(provider_service_instance_id = self.id)
for link in links:
@@ -46,15 +51,19 @@
@property
def c_tag(self):
+ log.warning(
+ 'VOLTServiceInstance.c_tag is DEPRECATED, use get_westbound_service_instance_properties instead')
return self.subscriber.c_tag
@property
def s_tag(self):
+ log.warning(
+ 'VOLTServiceInstance.s_tag is DEPRECATED, use get_westbound_service_instance_properties instead')
if not self.subscriber:
raise Exception("vOLT %s has no subscriber" % self.name)
- olt_device = self.stub.OLTDevice.objects.get(name = self.subscriber.olt_device)
- olt_port = self.stub.PONPort.objects.get(name = self.subscriber.olt_port, volt_device_id=olt_device.id)
+ olt_device = self.stub.OLTDevice.objects.get(name=self.subscriber.olt_device)
+ olt_port = self.stub.PONPort.objects.get(name=self.subscriber.olt_port, volt_device_id=olt_device.id)
if olt_port:
return olt_port.s_tag
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index efe40ff..7845d14 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -609,11 +609,11 @@
import convenience.cordsubscriberroot
import convenience.voltserviceinstance
import convenience.vsgserviceinstance
+import convenience.serviceinstance
import convenience.vrouterservice
import convenience.vroutertenant
import convenience.vrouterapp
import convenience.service
-import convenience.tenant
import convenience.onosapp
import convenience.controller
import convenience.user