RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
-RUN apt-get update && apt-get install -y git python3 python3-jsonschema \
+RUN apt-get update && apt-get install -y git python3 \
python3-pymongo python3-yaml python3-pip python3-keystoneclient \
&& python3 -m pip install pip --upgrade \
- && python3 -m pip install aiokafka aiohttp cherrypy==18.1.2 keystoneauth1 requests \
+ && python3 -m pip install aiokafka aiohttp cherrypy==18.1.2 keystoneauth1 requests jsonschema==3.2.0 \
&& mkdir -p /app/storage/kafka && mkdir -p /app/log
# OSM_COMMON
python3 -m pip install -U pip
python3 -m pip install cherrypy==18.1.2
python3 -m pip install keystoneauth1
+python3 -m pip install jsonschema==3.2.0
#Creation of log folder
mkdir -p /var/log/osm
from osm_nbi.descriptor_topics import VnfdTopic, NsdTopic, PduTopic, NstTopic, VnfPkgOpTopic
from osm_nbi.instance_topics import NsrTopic, VnfrTopic, NsLcmOpTopic, NsiTopic, NsiLcmOpTopic
from osm_nbi.pmjobs_topics import PmJobsTopic
+from osm_nbi.subscription_topics import NslcmSubscriptionsTopic
from base64 import b64encode
from os import urandom # , path
from threading import Lock
"nsis": NsiTopic,
"nsilcmops": NsiLcmOpTopic,
"vnfpkgops": VnfPkgOpTopic,
+ "nslcm_subscriptions": NslcmSubscriptionsTopic,
# [NEW_TOPIC]: add an entry here
# "pm_jobs": PmJobsTopic will be added manually because it needs other parameters
}
"ROLE_PERMISSION": "vnf_instances:id:"
}
},
+ "subscriptions": {"METHODS": ("GET", "POST"),
+ "ROLE_PERMISSION": "ns_subscriptions:",
+ "<ID>": {"METHODS": ("GET", "DELETE"),
+ "ROLE_PERMISSION": "ns_subscriptions:id:"
+ }
+ },
}
},
"nst": {
engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
indata = self._format_in(kwargs)
engine_topic = topic
- if topic == "subscriptions":
- engine_topic = main_topic + "_" + topic
+
if item and topic != "pm_jobs":
engine_topic = item
if engine_topic == "vims": # TODO this is for backward compatibility, it will be removed in the future
engine_topic = "vim_accounts"
+ if topic == "subscriptions":
+ engine_topic = main_topic + "_" + topic
+
if method == "GET":
if item in ("nsd_content", "package_content", "artifacts", "vnfd", "nsd", "nst", "nst_content"):
if item in ("vnfd", "nsd", "nst"):
self._set_location_header(main_topic, version, "vnfpkg_op_occs", _id)
outdata = {"id": _id}
cherrypy.response.status = HTTPStatus.ACCEPTED.value
+ elif topic == "subscriptions":
+ _id, _ = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs)
+ self._set_location_header(main_topic, version, topic, _id)
+ link = {}
+ link["self"] = cherrypy.response.headers["Location"]
+ outdata = {"id": _id, "filter": indata["filter"], "callbackUri": indata["CallbackUri"],
+ "_links": link}
+ cherrypy.response.status = HTTPStatus.CREATED.value
else:
_id, op_id = self.engine.new_item(rollback, engine_session, engine_topic, indata, kwargs,
cherrypy.request.headers)
if rollback_item.get("operation") == "set":
self.engine.db.set_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
rollback_item["content"], fail_on_empty=False)
+ elif rollback_item.get("operation") == "del_list":
+ self.engine.db.del_list(rollback_item["topic"], rollback_item["filter"],
+ fail_on_empty=False)
else:
self.engine.db.del_one(rollback_item["topic"], {"_id": rollback_item["_id"]},
fail_on_empty=False)
--- /dev/null
+# Copyright 2020 Preethika P(Tata Elxsi)
+#
+# 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.
+
+__author__ = "Preethika P,preethika.p@tataelxsi.co.in"
+
+
+import requests
+from osm_nbi.base_topic import BaseTopic, EngineException
+from osm_nbi.validation import subscription
+
+
+class CommonSubscriptions(BaseTopic):
+ topic = "subscriptions"
+ topic_msg = None
+
+ def format_on_new(self, content, project_id=None, make_public=False):
+ super().format_on_new(content, project_id=project_id, make_public=make_public)
+
+ # TODO check how to release Engine.write_lock during the check
+ def _check_endpoint(url, auth):
+ """
+ Checks if the notification endpoint is valid
+ :param url: the notification end
+ :param auth: contains the aunthentication details with type basic
+ """
+ try:
+ if auth is None:
+ response = requests.get(url, timeout=5)
+ if response.status_code != 204:
+ raise EngineException("Cannot access to the notification URL '{}',received {}: {}"
+ .format(url, response.status_code, response.content))
+ elif auth["authType"] == "basic":
+ username = auth["paramsBasic"].get("userName")
+ password = auth["paramsBasic"].get("password")
+ response = requests.get(url, auth=(username, password), timeout=5)
+ if response.status_code != 204:
+ raise EngineException("Cannot access to the notification URL '{}',received {}: {}"
+ .format(url, response.status_code, response.content))
+ except requests.exceptions.RequestException as e:
+ error_text = type(e).__name__ + ": " + str(e)
+ raise EngineException("Cannot access to the notification URL '{}': {}".format(url, error_text))
+ url = content["CallbackUri"]
+ auth = content.get("authentication")
+ _check_endpoint(url, auth)
+ content["schema_version"] = schema_version = "1.1"
+ if auth is not None and auth["authType"] == "basic":
+ if content["authentication"]["paramsBasic"].get("password"):
+ content["authentication"]["paramsBasic"]["password"] = \
+ self.db.encrypt(content["authentication"]["paramsBasic"]["password"],
+ schema_version=schema_version, salt=content["_id"])
+ return None
+
+ def new(self, rollback, session, indata=None, kwargs=None, headers=None):
+ """
+ Uses BaseTopic.new to create entry into db
+ Once entry is made into subscriptions,mapper function is invoked
+ """
+ _id, op_id = BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers)
+ rollback.append({"topic": "mapped_subscriptions", "operation": "del_list", "filter": {"reference": _id}})
+ self._subscription_mapper(_id, indata, table="mapped_subscriptions")
+ return _id, op_id
+
+ def delete_extra(self, session, _id, db_content, not_send_msg=None):
+ """
+ Deletes the mapped_subscription entry for this particular subscriber
+ :param _id: subscription_id deleted
+ """
+ super().delete_extra(session, _id, db_content, not_send_msg)
+ filter_q = {}
+ filter_q["reference"] = _id
+ self.db.del_list("mapped_subscriptions", filter_q)
+
+
+class NslcmSubscriptionsTopic(CommonSubscriptions):
+ schema_new = subscription
+
+ def _subscription_mapper(self, _id, data, table):
+ """
+ Performs data transformation on subscription request
+ :param data: data to be trasformed
+ :param table: table in which transformed data are inserted
+ """
+ formatted_data = []
+ formed_data = {"reference": data.get("_id"),
+ "CallbackUri": data.get("CallbackUri")}
+ if data.get("authentication"):
+ formed_data.update({"authentication": data.get("authentication")})
+ if data.get("filter"):
+ if data["filter"].get("nsInstanceSubscriptionFilter"):
+ key = list(data["filter"]["nsInstanceSubscriptionFilter"].keys())[0]
+ identifier = data["filter"]["nsInstanceSubscriptionFilter"][key]
+ formed_data.update({"identifier": identifier})
+ if data["filter"].get("notificationTypes"):
+ for elem in data["filter"].get("notificationTypes"):
+ update_dict = formed_data.copy()
+ update_dict["notificationType"] = elem
+ if elem == "NsIdentifierCreationNotification":
+ update_dict["operationTypes"] = "INSTANTIATE"
+ update_dict["operationStates"] = "ANY"
+ formatted_data.append(update_dict)
+ elif elem == "NsIdentifierDeletionNotification":
+ update_dict["operationTypes"] = "TERMINATE"
+ update_dict["operationStates"] = "ANY"
+ formatted_data.append(update_dict)
+ elif elem == "NsLcmOperationOccurrenceNotification":
+ if "operationTypes" in data["filter"].keys():
+ update_dict["operationTypes"] = data["filter"]["operationTypes"]
+ else:
+ update_dict["operationTypes"] = "ANY"
+ if "operationStates" in data["filter"].keys():
+ update_dict["operationStates"] = data["filter"]["operationStates"]
+ else:
+ update_dict["operationStates"] = "ANY"
+ formatted_data.append(update_dict)
+ elif elem == "NsChangeNotification":
+ if "nsComponentTypes" in data["filter"].keys():
+ update_dict["nsComponentTypes"] = data["filter"]["nsComponentTypes"]
+ else:
+ update_dict["nsComponentTypes"] = "ANY"
+ if "lcmOpNameImpactingNsComponent" in data["filter"].keys():
+ update_dict["lcmOpNameImpactingNsComponent"] = \
+ data["filter"]["lcmOpNameImpactingNsComponent"]
+ else:
+ update_dict["lcmOpNameImpactingNsComponent"] = "ANY"
+ if "lcmOpOccStatusImpactingNsComponent" in data["filter"].keys():
+ update_dict["lcmOpOccStatusImpactingNsComponent"] = \
+ data["filter"]["lcmOpOccStatusImpactingNsComponent"]
+ else:
+ update_dict["lcmOpOccStatusImpactingNsComponent"] = "ANY"
+ formatted_data.append(update_dict)
+ self.db.create_list(table, formatted_data)
+ return None
# PROJECTS
topics_with_quota = ["vnfds", "nsds", "slice_templates", "pduds", "ns_instances", "slice_instances", "vim_accounts",
- "wim_accounts", "sdn_controllers", "k8sclusters", "k8srepos", "osmrepos"]
+ "wim_accounts", "sdn_controllers", "k8sclusters", "k8srepos", "osmrepos", "ns_subscriptions"]
project_new_schema = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "New project schema for administrators",
}
+nsinstancesubscriptionfilter_schema = {
+ "title": "instance identifier schema",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "nsdIds": {"type": "array"},
+ "vnfdIds": {"type": "array"},
+ "pnfdIds": {"type": "array"},
+ "nsInstanceIds": {"type": "array"},
+ "nsInstanceNames": {"type": "array"},
+ },
+}
+
+nslcmsub_schema = {
+ "title": "nslcmsubscription input schema",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "nsInstanceSubscriptionFilter": nsinstancesubscriptionfilter_schema,
+ "notificationTypes": {
+ "type": "array",
+ "items": {
+ "enum": ['NsLcmOperationOccurrenceNotification', 'NsChangeNotification',
+ 'NsIdentifierCreationNotification', 'NsIdentifierDeletionNotification']
+ }
+ },
+ "operationTypes": {
+ "type": "array",
+ "items": {
+ "enum": ['INSTANTIATE', 'SCALE', 'TERMINATE', 'UPDATE', 'HEAL']
+ }
+ },
+ "operationStates": {
+ "type": "array",
+ "items": {
+ "enum": ['PROCESSING', 'COMPLETED', 'PARTIALLY_COMPLETED', 'FAILED',
+ 'FAILED_TEMP', 'ROLLING_BACK', 'ROLLED_BACK']
+ }
+ },
+ "nsComponentTypes": {
+ "type": "array",
+ "items": {
+ "enum": ['VNF', 'NS', 'PNF']
+ }
+ },
+ "lcmOpNameImpactingNsComponent": {
+ "type": "array",
+ "items": {
+ "enum": ['VNF_INSTANTIATE', 'VNF_SCALE', 'VNF_SCALE_TO_LEVEL', 'VNF_CHANGE_FLAVOUR',
+ 'VNF_TERMINATE', 'VNF_HEAL', 'VNF_OPERATE', 'VNF_CHANGE_EXT_CONN', 'VNF_MODIFY_INFO',
+ 'NS_INSTANTIATE', 'NS_SCALE', 'NS_UPDATE', 'NS_TERMINATE', 'NS_HEAL']
+ }
+ },
+ "lcmOpOccStatusImpactingNsComponent": {
+ "type": "array",
+ "items": {
+ "enum": ['START', 'COMPLETED', 'PARTIALLY_COMPLETED', 'FAILED', 'ROLLED_BACK']
+ }
+ },
+ },
+ "allOf": [
+ {
+ "if": {
+ "properties": {
+ "notificationTypes": {
+ "contains": {"const": "NsLcmOperationOccurrenceNotification"}
+ }
+ },
+ },
+ "then": {
+ "anyOf": [
+ {"required": ["operationTypes"]},
+ {"required": ["operationStates"]},
+ ]
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "notificationTypes": {
+ "contains": {"const": "NsChangeNotification"}
+ }
+ },
+ },
+ "then": {
+ "anyOf": [
+ {"required": ["nsComponentTypes"]},
+ {"required": ["lcmOpNameImpactingNsComponent"]},
+ {"required": ["lcmOpOccStatusImpactingNsComponent"]},
+ ]
+ }
+ }
+ ]
+}
+
+authentication_schema = {
+ "title": "authentication schema for subscription",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "authType": {"enum": ["basic"]},
+ "paramsBasic": {
+ "type": "object",
+ "properties": {
+ "userName": shortname_schema,
+ "password": passwd_schema,
+ },
+ },
+ },
+}
+
+subscription = {
+ "title": "subscription input schema",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "filter": nslcmsub_schema,
+ "CallbackUri": description_schema,
+ "authentication": authentication_schema
+ },
+ "required": ["CallbackUri"],
+}
+
class ValidationError(Exception):
def __init__(self, message, http_code=HTTPStatus.UNPROCESSABLE_ENTITY):
# under the License.
CherryPy==18.1.2
-jsonschema
+jsonschema==3.2.0
PyYAML
python-keystoneclient
requests
install_requires=[
'CherryPy==18.1.2',
'osm-common @ git+https://osm.etsi.org/gerrit/osm/common.git#egg=osm-common',
- 'jsonschema',
+ 'jsonschema==3.2.0',
'PyYAML',
'osm-im @ git+https://osm.etsi.org/gerrit/osm/IM.git#egg=osm-im',
'python-keystoneclient',