[CORD-3100] Refactoring and documenting the ONOS Service
Change-Id: Ida83935798a2a99b538f6ccdc55cc26f3148ffe3
diff --git a/xos/synchronizer/steps/sync_onos_app.py b/xos/synchronizer/steps/sync_onos_app.py
new file mode 100644
index 0000000..6288243
--- /dev/null
+++ b/xos/synchronizer/steps/sync_onos_app.py
@@ -0,0 +1,230 @@
+
+# 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
+import requests
+from requests.auth import HTTPBasicAuth
+from synchronizers.new_base.syncstep import SyncStep, DeferredException, model_accessor
+from synchronizers.new_base.modelaccessor import ONOSApp, ServiceInstance, ServiceInstanceAttribute
+
+from xosconfig import Config
+from multistructlog import create_logger
+
+from helpers import Helpers
+
+log = create_logger(Config().get('logging'))
+log.info("config file", file=Config().get_config_file())
+
+class SyncONOSApp(SyncStep):
+ provides = [ONOSApp]
+ observes = [ONOSApp, ServiceInstanceAttribute]
+
+ def get_service_instance_attribute(self, o):
+ # NOTE this method is defined in the core convenience methods for service_instances
+ svc = ServiceInstance.objects.get(id=o.id)
+ return svc.serviceinstanceattribute_dict
+
+ def check_app_dependencies(self, deps):
+ """
+ Check if all the dependencies required by this application are installed
+ :param deps: comma separated list of application names
+ :return: bool
+ """
+ for dep in [x.strip() for x in str(deps).split(',') if x is not ""]:
+ try:
+ app = ONOSApp.objects.get(app_id=dep)
+ if not app.backend_code == 1:
+ # backend_code == 1 means that the app has been pushed
+ return False
+ except IndexError, e:
+ return False
+ return True
+
+ def add_config(self, o):
+ log.info("Adding config %s" % o.name, model=o.tologdict())
+ # getting onos url and auth
+ onos_url = "%s:%s" % (Helpers.format_url(o.service_instance.leaf_model.owner.leaf_model.rest_hostname), o.service_instance.leaf_model.owner.leaf_model.rest_port)
+ onos_basic_auth = HTTPBasicAuth(o.service_instance.leaf_model.owner.leaf_model.rest_username, o.service_instance.leaf_model.owner.leaf_model.rest_password)
+
+ # push configs (if any)
+ url = o.name
+ if url[0] == "/":
+ # strip initial /
+ url = url[1:]
+
+ url = '%s/%s' % (onos_url, url)
+ value = json.loads(o.value)
+ request = requests.post(url, json=value, auth=onos_basic_auth)
+
+ if request.status_code != 200:
+ log.error("Request failed", response=request.text)
+ raise Exception("Failed to add config %s in ONOS: %s" % (url, request.text))
+
+ def activate_app(self, o, onos_url, onos_basic_auth):
+ log.info("Activating app %s" % o.app_id)
+ url = '%s/onos/v1/applications/%s/active' % (onos_url, o.app_id)
+ request = requests.post(url, auth=onos_basic_auth)
+
+ if request.status_code != 200:
+ log.error("Request failed", response=request.text)
+ raise Exception("Failed to add application %s to ONOS: %s" % (url, request.text))
+
+ url = '%s/onos/v1/applications/%s' % (onos_url, o.app_id)
+ request = requests.get(url, auth=onos_basic_auth)
+
+ if request.status_code != 200:
+ log.error("Request failed", response=request.text)
+ raise Exception("Failed to read application %s from ONOS: %s" % (url, request.text))
+ else:
+ o.version = request.json()["version"]
+
+ def check_app_installed(self, o, onos_url, onos_basic_auth):
+ url = '%s/onos/v1/applications/%s' % (onos_url, o.app_id)
+ request = requests.get(url, auth=onos_basic_auth)
+
+ if request.status_code == 200:
+ if "version" in request.json() and o.version == request.json()["version"]:
+ return True
+ else:
+ # uninstall the application
+ self.uninstall_app(o, onos_url, onos_basic_auth)
+ return False
+ if request.status_code == 404:
+ # app is not installed at all
+ return False
+ else:
+ log.error("Request failed", response=request.text)
+ raise Exception("Failed to read application %s from ONOS: %s" % (url, request.text))
+
+ def install_app(self, o, onos_url, onos_basic_auth):
+ log.info("Installing app from url %s" % o.url)
+
+ # check is the already installed app is the correct version (if it has no app_id is not installed)
+ is_installed = False
+ if o.app_id and o.app_id is not None:
+ is_installed = self.check_app_installed(o, onos_url, onos_basic_auth)
+
+ if is_installed:
+ # if the app is already installed we don't need to do anything
+ return
+
+ if not o.version or o.version is None:
+ # TODO move this validation in the model.py (if the url is there version must there and app_id must not)
+ raise Exception('You need to specify a version')
+ data = {
+ 'activate': True,
+ 'url': o.url
+ }
+ url = '%s/onos/v1/applications' % onos_url
+ request = requests.post(url, json=data, auth=onos_basic_auth)
+
+ if request.status_code != 200:
+ log.error("Request failed", response=request.text)
+ raise Exception("Failed to add application %s to ONOS: %s" % (url, request.text))
+
+ o.app_id = request.json()["name"]
+
+ url = '%s/onos/v1/applications/%s' % (onos_url, o.app_id)
+ request = requests.get(url, auth=onos_basic_auth)
+
+ if request.status_code != 200:
+ log.error("Request failed", response=request.text)
+ raise Exception("Failed to read application %s from ONOS: %s" % (url, request.text))
+ else:
+ if o.version != request.json()["version"]:
+ raise Exception("The version of %s you installed (%s) is not the same you requested (%s)" % (o.app_id, request.json()["version"], o.version))
+
+ def sync_record(self, o):
+ log.info("Sync'ing", model=o.tologdict())
+ if hasattr(o, 'service_instance'):
+ # this is a ServiceInstanceAttribute model just push the config
+ if 'ONOSApp' in o.service_instance.leaf_model.class_names:
+ return self.add_config(o)
+ return # if it's not an ONOSApp do nothing
+
+ if not self.check_app_dependencies(o.dependencies):
+ raise DeferredException('Deferring installation of ONOSApp with id %s as dependencies are not met' % o.id)
+
+ # getting onos url and auth
+ onos_url = "%s:%s" % (Helpers.format_url(o.owner.leaf_model.rest_hostname), o.owner.leaf_model.rest_port)
+ onos_basic_auth = HTTPBasicAuth(o.owner.leaf_model.rest_username, o.owner.leaf_model.rest_password)
+
+ # activate app (bundled in onos)
+ if not o.url or o.url is None:
+ self.activate_app(o, onos_url, onos_basic_auth)
+ # install an app from a remote source
+ if o.url and o.url is not None:
+ self.install_app(o, onos_url, onos_basic_auth)
+
+ def delete_config(self, o):
+ log.info("Deleting config %s" % o.name)
+ # getting onos url and auth
+ onos_app = o.service_instance.leaf_model
+ onos_url = "%s:%s" % (Helpers.format_url(onos_app.owner.leaf_model.rest_hostname), onos_app.owner.leaf_model.rest_port)
+ onos_basic_auth = HTTPBasicAuth(onos_app.owner.leaf_model.rest_username, onos_app.owner.leaf_model.rest_password)
+
+ url = o.name
+ if url[0] == "/":
+ # strip initial /
+ url = url[1:]
+
+ url = '%s/%s' % (onos_url, url)
+ request = requests.delete(url, auth=onos_basic_auth)
+
+ if request.status_code != 204:
+ log.error("Request failed", response=request.text)
+ raise Exception("Failed to remove config %s from ONOS: %s" % (url, request.text))
+
+ def uninstall_app(self,o, onos_url, onos_basic_auth):
+ log.info("Uninstalling app %s" % o.app_id)
+ url = '%s/onos/v1/applications/%s' % (onos_url, o.app_id)
+
+ request = requests.delete(url, auth=onos_basic_auth)
+
+ if request.status_code != 204:
+ log.error("Request failed", response=request.text)
+ raise Exception("Failed to delete application %s from ONOS: %s" % (url, request.text))
+
+ def deactivate_app(self, o, onos_url, onos_basic_auth):
+ log.info("Deactivating app %s" % o.app_id)
+ url = '%s/onos/v1/applications/%s/active' % (onos_url, o.app_id)
+
+ request = requests.delete(url, auth=onos_basic_auth)
+
+ if request.status_code != 204:
+ log.error("Request failed", response=request.text)
+ raise Exception("Failed to deactivate application %s from ONOS: %s" % (url, request.text))
+
+ def delete_record(self, o):
+
+ if hasattr(o, 'service_instance'):
+ # this is a ServiceInstanceAttribute model
+ if 'ONOSApp' in o.service_instance.leaf_model.class_names:
+ return self.delete_config(o)
+ return # if it's not related to an ONOSApp do nothing
+
+ # NOTE if it is an ONOSApp we don't care about the ServiceInstanceAttribute
+ # as the reaper will delete it
+
+ # getting onos url and auth
+ onos_url = "%s:%s" % (Helpers.format_url(o.owner.leaf_model.rest_hostname), o.owner.leaf_model.rest_port)
+ onos_basic_auth = HTTPBasicAuth(o.owner.leaf_model.rest_username, o.owner.leaf_model.rest_password)
+
+ # deactivate an app (bundled in onos)
+ if not o.url or o.url is None:
+ self.deactivate_app(o, onos_url, onos_basic_auth)
+ # uninstall an app from a remote source, only if it has been activated before
+ if o.url and o.url is not None:
+ self.uninstall_app(o, onos_url, onos_basic_auth)