aboutsummaryrefslogtreecommitdiffstats
path: root/osdf
diff options
context:
space:
mode:
authorAnkitkumar Patel <ankit@research.att.com>2018-02-11 17:51:13 -0500
committerAnkitkumar Patel <ankit@research.att.com>2018-02-11 17:52:51 -0500
commit0b855c08fd98fb8fa0f4bc40d8df430c897b4bad (patch)
treeefdd3c7ab31be64080dd71951a64d13f0ba493de /osdf
parentbb8471cae394aa6ff0af8ba3e5354f3b121c56fc (diff)
Re-org folders, onboard test folder, test config
Reorganized the folder structure. Onboarded testcases. Added test config. Issue-ID: OPTFRA-74 Change-Id: I97882a162a405a18ffd287495039e15ae9d0ad7b Signed-off-by: Ankitkumar Patel <ankit@research.att.com>
Diffstat (limited to 'osdf')
-rwxr-xr-xosdf/__init__.py45
-rw-r--r--osdf/adapters/__init__.py0
-rw-r--r--osdf/adapters/database/OracleDB.py32
-rw-r--r--osdf/adapters/database/PostgresDB.py31
-rw-r--r--osdf/adapters/database/VerticaDB.py55
-rw-r--r--osdf/adapters/database/__init__.py0
-rw-r--r--osdf/adapters/dcae/__init__.py0
-rwxr-xr-xosdf/adapters/dcae/message_router.py100
-rw-r--r--osdf/adapters/local_data/__init__.py0
-rw-r--r--osdf/adapters/local_data/local_policies.py40
-rw-r--r--osdf/adapters/policy/__init__.py0
-rw-r--r--osdf/adapters/policy/interface.py204
-rw-r--r--osdf/adapters/policy/utils.py58
-rw-r--r--osdf/adapters/request_parsing/__init__.py0
-rw-r--r--osdf/adapters/request_parsing/placement.py33
-rw-r--r--osdf/adapters/sdc/__init__.py0
-rwxr-xr-xosdf/adapters/sdc/asdc.py40
-rw-r--r--osdf/adapters/sdc/constraint_handler.py81
-rw-r--r--osdf/config/__init__.py32
-rw-r--r--osdf/config/base.py36
-rw-r--r--osdf/config/credentials.py60
-rw-r--r--osdf/config/loader.py51
-rw-r--r--osdf/logging/__init__.py0
-rwxr-xr-xosdf/logging/osdf_logging.py229
-rwxr-xr-xosdf/models/api/common.py54
-rw-r--r--osdf/models/api/placementRequest.py124
-rw-r--r--osdf/models/api/placementResponse.py57
-rw-r--r--osdf/models/policy/cmso/xacml/placementPolicies.xcore718
-rw-r--r--osdf/models/policy/placement/xacml/placementPolicies.xcore728
-rw-r--r--osdf/operation/__init__.py0
-rw-r--r--osdf/operation/error_handling.py93
-rw-r--r--osdf/operation/exceptions.py40
-rw-r--r--osdf/operation/responses.py39
-rw-r--r--osdf/optimizers/__init__.py0
-rw-r--r--osdf/optimizers/licenseopt/__init__.py0
-rw-r--r--osdf/optimizers/licenseopt/simple_license_allocation.py56
-rw-r--r--osdf/optimizers/placementopt/__init__.py0
-rw-r--r--osdf/optimizers/placementopt/conductor/__init__.py0
-rw-r--r--osdf/optimizers/placementopt/conductor/api_builder.py123
-rw-r--r--osdf/optimizers/placementopt/conductor/conductor.py186
-rw-r--r--osdf/optimizers/placementopt/conductor/remote_opt_processor.py79
-rw-r--r--osdf/optimizers/placementopt/conductor/translation.py216
-rwxr-xr-xosdf/templates/cms_opt_request.jsont35
-rwxr-xr-xosdf/templates/cms_opt_request.jsont_1707_v167
-rwxr-xr-xosdf/templates/cms_opt_request_1702.jsont63
-rw-r--r--osdf/templates/cms_opt_response.jsont8
-rwxr-xr-xosdf/templates/conductor_interface.json81
-rw-r--r--osdf/templates/license_opt_request.jsont6
-rwxr-xr-xosdf/templates/plc_opt_request.jsont142
-rwxr-xr-xosdf/templates/plc_opt_response.jsont14
-rwxr-xr-xosdf/templates/policy_request.jsont3
-rwxr-xr-xosdf/templates/test_cms_nb_req_from_client.jsont19
-rwxr-xr-xosdf/templates/test_plc_nb_req_from_client.jsont52
-rw-r--r--osdf/utils/__init__.py0
-rw-r--r--osdf/utils/data_conversion.py62
-rw-r--r--osdf/utils/data_types.py30
-rw-r--r--osdf/utils/interfaces.py90
-rw-r--r--osdf/utils/local_processing.py43
-rw-r--r--osdf/utils/programming_utils.py105
-rw-r--r--osdf/webapp/__init__.py0
-rw-r--r--osdf/webapp/appcontroller.py47
61 files changed, 4507 insertions, 0 deletions
diff --git a/osdf/__init__.py b/osdf/__init__.py
new file mode 100755
index 0000000..d0993ae
--- /dev/null
+++ b/osdf/__init__.py
@@ -0,0 +1,45 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+
+"""Core functions for OSDF Application, including flask app"""
+
+from jinja2 import Template
+
+
+end_point_auth_mapping = { # map a URL endpoint to auth group
+ "cmscheduler": "CMScheduler",
+ "placement": "Placement",
+}
+
+userid_suffix, passwd_suffix = "Username", "Password"
+auth_groups = set(end_point_auth_mapping.values())
+
+ERROR_TEMPLATE = Template("""
+{
+ "serviceException": {
+ "text": "{{ description }}"
+ }
+}
+""")
+
+ACCEPTED_MESSAGE_TEMPLATE = Template("""
+{
+ "requestId": "{{ request_id }}",
+ "text": "{{ description }}"
+}
+""")
diff --git a/osdf/adapters/__init__.py b/osdf/adapters/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/adapters/__init__.py
diff --git a/osdf/adapters/database/OracleDB.py b/osdf/adapters/database/OracleDB.py
new file mode 100644
index 0000000..655dd27
--- /dev/null
+++ b/osdf/adapters/database/OracleDB.py
@@ -0,0 +1,32 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 cx_Oracle
+
+from osdf.utils.programming_utils import MetaSingleton
+
+
+class OracleDB(metaclass=MetaSingleton):
+ conn, cur = None, None
+
+ def connect(self, host=None, sid=None, user=None, passwd=None, port=5432):
+ if self.conn is None:
+ tns_info = cx_Oracle.makedsn(host=host, port=port, sid=sid)
+ self.conn = cx_Oracle.connect(user=user, password=passwd, dsn=tns_info, threaded=True)
+ self.cur = self.conn.cursor()
+ return self.conn, self.cur
diff --git a/osdf/adapters/database/PostgresDB.py b/osdf/adapters/database/PostgresDB.py
new file mode 100644
index 0000000..6689566
--- /dev/null
+++ b/osdf/adapters/database/PostgresDB.py
@@ -0,0 +1,31 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 psycopg2
+
+from osdf.utils.programming_utils import MetaSingleton
+
+
+class PostgresDB(metaclass=MetaSingleton):
+ conn, cur = None, None
+
+ def connect(self, host=None, db=None, user=None, passwd=None, port=5432):
+ if self.conn is None:
+ self.conn = psycopg2.connect(host=host, port=port, user=user, password=passwd, database=db)
+ self.cur = self.conn.cursor()
+ return self.conn, self.cur
diff --git a/osdf/adapters/database/VerticaDB.py b/osdf/adapters/database/VerticaDB.py
new file mode 100644
index 0000000..ad961d7
--- /dev/null
+++ b/osdf/adapters/database/VerticaDB.py
@@ -0,0 +1,55 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 jaydebeapi
+import sqlalchemy.pool as pool
+
+from jaydebeapi import _DEFAULT_CONVERTERS, _java_to_py
+from osdf.utils.programming_utils import MetaSingleton
+from osdf.config.base import osdf_config
+
+_DEFAULT_CONVERTERS.update({'BIGINT': _java_to_py('longValue')})
+
+
+class VerticaDB(metaclass=MetaSingleton):
+ connection_pool = None
+
+ def get_connection(self):
+ p = self.get_config_params()
+ c = jaydebeapi.connect(
+ 'com.vertica.jdbc.Driver',
+ 'jdbc:vertica://{}:{}/{}'.format(p['host'], p['port'], p['db']),
+ {'user': p['user'], 'password': p['passwd'], 'CHARSET': 'UTF8'},
+ jars=[p['db_driver']]
+ )
+ return c
+
+ def get_config_params(self):
+ config = osdf_config["deployment"]
+ host, port, db = config["verticaHost"], config["verticaPort"], config.get("verticaDB")
+ user, passwd = config["verticaUsername"], config["verticaPassword"]
+ jar_path = osdf_config['core']['osdf_system']['vertica_jar']
+ params = dict(host=host, db=db, user=user, passwd=passwd, port=port, db_driver=jar_path)
+ return params
+
+ def connect(self):
+ if self.connection_pool is None:
+ self.connection_pool = pool.QueuePool(self.get_connection, max_overflow=10, pool_size=5, recycle=600)
+ conn = self.connection_pool.connect()
+ cursor = conn.cursor()
+ return conn, cursor
diff --git a/osdf/adapters/database/__init__.py b/osdf/adapters/database/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/adapters/database/__init__.py
diff --git a/osdf/adapters/dcae/__init__.py b/osdf/adapters/dcae/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/adapters/dcae/__init__.py
diff --git a/osdf/adapters/dcae/message_router.py b/osdf/adapters/dcae/message_router.py
new file mode 100755
index 0000000..e495331
--- /dev/null
+++ b/osdf/adapters/dcae/message_router.py
@@ -0,0 +1,100 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 requests
+from osdf.utils.data_types import list_like
+from osdf.operation.exceptions import MessageBusConfigurationException
+
+
+class MessageRouterClient(object):
+ def __init__(self,
+ dmaap_url=None,
+ mr_host_base_urls=None,
+ topic=None,
+ consumer_group=None, consumer_id=None,
+ timeout_ms=15000, fetch_limit=1000,
+ userid=None, passwd=None):
+ """
+ :param dmaap_url: protocol, host and port; mostly for UEB
+ (e.g. https://dcae-msrt-ftl.homer.att.com:3905/)
+ :param mr_host_base_urls: for DMaaP, we get a topic URL (base_url + events/topic_name)
+ (e.g. https://dcae-msrt-ftl.homer.att.com:3905/events/com.att.dcae.dmaap.FTL.SNIRO-CM-SCHEDULER-RESPONSE)
+ :param consumer_group: DMaaP/UEB consumer group (unique for each subscriber; required for GET)
+ :param consumer_id: DMaaP/UEB consumer ID (unique for each thread/process for a subscriber; required for GET)
+ :param timeout_ms: (optional, default 15 seconds or 15,000 ms) server-side timeout for GET request
+ :param fetch_limit: (optional, default 1000 messages per request for GET), ignored for "POST"
+ :param userid: (optional, userid for HTTP basic authentication)
+ :param passwd: (optional, password for HTTP basic authentication)
+ """
+ mr_error = MessageBusConfigurationException
+ if dmaap_url is None: # definitely not DMaaP, so use UEB mode
+ self.is_dmaap = False
+ if not (mr_host_base_urls and list_like(mr_host_base_urls)):
+ raise mr_error("Not a DMaaP or UEB configuration")
+ if not topic:
+ raise mr_error("Invalid topic: '{}'",format(topic))
+ self.topic_urls = ["{}/events/{}".format(base_url, topic) for base_url in mr_host_base_urls]
+ else:
+ self.is_dmaap = True
+ self.topic_urls = [dmaap_url]
+
+ self.timeout_ms = timeout_ms
+ self.fetch_limit = fetch_limit
+ self.auth = (userid, passwd) if userid and passwd else None
+ self.consumer_group = consumer_group
+ self.consumer_id = consumer_id
+
+ def get(self, outputjson=True):
+ """Fetch messages from message router (DMaaP or UEB)
+ :param outputjson: (optional, specifies if response is expected to be in json format), ignored for "POST"
+ :return: response as a json object (if outputjson is True) or as a string
+ """
+ url_fmt = "{topic_url}/{cgroup}/{cid}?timeout={timeout_ms}&limit={limit}"
+ urls = [url_fmt.format(topic_url=x, timeout_ms=self.timeout_ms, limit=self.fetch_limit,
+ cgroup=self.consumer_group, cid=self.consumer_id) for x in self.topic_urls]
+ for url in urls[:-1]:
+ try:
+ return self.http_request(method='GET', url=url, outputjson=outputjson)
+ except:
+ pass
+ return self.http_request(method='GET', url=urls[-1], outputjson=outputjson)
+
+ def post(self, msg, inputjson=True):
+ for url in self.topic_urls[:-1]:
+ try:
+ return self.http_request(method='POST', url=url, inputjson=inputjson, msg=msg)
+ except:
+ pass
+ return self.http_request(method='POST', url=self.topic_urls[-1], inputjson=inputjson, msg=msg)
+
+ def http_request(self, url, method, inputjson=True, outputjson=True, msg=None, **kwargs):
+ """
+ Perform the actual URL request (GET or POST), and do error handling
+ :param url: full URL (including topic, limit, timeout, etc.)
+ :param method: GET or POST
+ :param inputjson: Specify whether input is in json format (valid only for POST)
+ :param outputjson: Is response expected in a json format
+ :param msg: content to be posted (valid only for POST)
+ :return: response as a json object (if outputjson or POST) or as a string; None if error
+ """
+ res = requests.request(url=url, method=method, auth=self.auth, **kwargs)
+ if res.status_code == requests.codes.ok:
+ return res.json() if outputjson or method == "POST" else res.content
+ else:
+ raise Exception("HTTP Response Error: code {}; headers:{}, content: {}".format(
+ res.status_code, res.headers, res.content))
diff --git a/osdf/adapters/local_data/__init__.py b/osdf/adapters/local_data/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/adapters/local_data/__init__.py
diff --git a/osdf/adapters/local_data/local_policies.py b/osdf/adapters/local_data/local_policies.py
new file mode 100644
index 0000000..c63ae5a
--- /dev/null
+++ b/osdf/adapters/local_data/local_policies.py
@@ -0,0 +1,40 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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
+
+
+def get_local_policies(local_policy_folder, local_policy_list, policy_id_list=None):
+ """
+ Get policies from a local file system.
+ Required for the following scenarios:
+ (a) doing work-arounds (e.g. if we are asked to drop some policies for testing purposes)
+ (b) work-arounds when policy platform is giving issues (e.g. if dev/IST policies are wiped out in an upgrade)
+ :param local_policy_folder: where the policy files are present
+ :param local_policy_list: list of local policies
+ :param policy_id_list: list of policies to get (if unspecified or None, get all)
+ :return: get policies
+ """
+ policies = []
+ for fname in local_policy_list: # ugly removal of .json from file name
+ if policy_id_list and fname[:-5] not in policy_id_list:
+ continue
+ with open(os.path.join(local_policy_folder, fname)) as fid:
+ policies.append(json.load(fid))
+ return policies
diff --git a/osdf/adapters/policy/__init__.py b/osdf/adapters/policy/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/adapters/policy/__init__.py
diff --git a/osdf/adapters/policy/interface.py b/osdf/adapters/policy/interface.py
new file mode 100644
index 0000000..4ddee15
--- /dev/null
+++ b/osdf/adapters/policy/interface.py
@@ -0,0 +1,204 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 base64
+import itertools
+import json
+
+
+from requests import RequestException
+from osdf.operation.exceptions import BusinessException
+from osdf.adapters.local_data.local_policies import get_local_policies
+from osdf.adapters.policy.utils import _regex_policy_name
+from osdf.config.base import osdf_config
+from osdf.logging.osdf_logging import audit_log, MH, metrics_log, error_log, debug_log
+from osdf.utils.interfaces import RestClient
+from osdf.optimizers.placementopt.conductor.api_builder import retrieve_node
+# from osdf.utils import data_mapping
+
+
+def get_by_name(rest_client, policy_name_list, wildcards=True):
+ policy_list = []
+ for policy_name in policy_name_list:
+ try:
+ query_name = policy_name
+ if wildcards:
+ query_name = _regex_policy_name(query_name)
+ policy_list.append(rest_client.request(json={"policyName": query_name}))
+ except RequestException as err:
+ audit_log.warn("Error in fetching policy: " + policy_name)
+ raise BusinessException("Cannot fetch policy {}: ".format(policy_name), err)
+ return policy_list
+
+
+def get_subscriber_name(req, pmain):
+ subs_name = retrieve_node(req, pmain['subscriber_name'])
+ if subs_name is None:
+ return "DEFAULT"
+ else:
+ subs_name_uc = subs_name.upper()
+ if subs_name_uc in ("DEFAULT", "NULL", ""):
+ subs_name = "DEFAULT"
+ return subs_name
+
+
+def get_subscriber_role(rest_client, req, pmain, service_name, scope):
+ """Make a request to policy and return subscriberRole
+ :param rest_client: rest client to make call
+ :param req: request object from MSO
+ :param pmain: main config that will have policy path information
+ :param service_name: the type of service to call: e.g. "vCPE
+ :param scope: the scope of policy to call: e.g. "OOF_HAS_vCPE".
+ :return: subscriberRole and provStatus retrieved from Subscriber policy
+ """
+ subscriber_role = "DEFAULT"
+ prov_status = []
+ subs_name = get_subscriber_name(req, pmain)
+ if subs_name == "DEFAULT":
+ return subscriber_role, prov_status
+
+ policy_subs = pmain['policy_subscriber']
+ policy_scope = {"policyName": "{}.*".format(scope),
+ "configAttributes": {
+ "serviceType": "{}".format(service_name),
+ "service": "{}".format(policy_subs)}
+ }
+ policy_list = []
+ try:
+ policy_list.append(rest_client.request(json=policy_scope))
+ except RequestException as err:
+ audit_log.warn("Error in fetching policy for {}: ".format(policy_subs))
+ return subscriber_role, prov_status
+
+ formatted_policies = []
+ for x in itertools.chain(*policy_list):
+ if x['config'] is None:
+ raise BusinessException("Config not found for policy with name %s" % x['policyName'])
+ else:
+ formatted_policies.append(json.loads(x['config']))
+
+ for policy in formatted_policies:
+ property_list = policy['content']['property']
+ for prop in property_list:
+ if subs_name in prop['subscriberName']:
+ subs_role_list = prop['subscriberRole']
+ prov_status = prop['provStatus']
+ if isinstance(subs_role_list, list): # as list is returned
+ return subs_role_list[0], prov_status
+ return subscriber_role, prov_status
+
+
+def get_by_scope(rest_client, req, config_local, type_service):
+ policy_list = []
+ pmain = config_local['policy_info'][type_service]
+ pscope = pmain['policy_scope']
+
+ model_name = retrieve_node(req, pscope['service_name'])
+ service_name = model_name
+ # service_name = data_mapping.get_request_service_type(req)
+ # if service_name is None:
+ # service_name = data_mapping.get_service_type(model_name)
+ scope = pscope['scope_{}'.format(service_name.lower())]
+ subscriber_role, prov_status = get_subscriber_role(rest_client, req, pmain, service_name, scope)
+ policy_type_list = pmain['policy_type_{}'.format(service_name.lower())]
+ for policy_type in policy_type_list:
+ policy_scope = {"policyName": "{}.*".format(scope),
+ "configAttributes": {
+ "serviceType": "{}".format(service_name),
+ "service": "{}".format(policy_type),
+ "subscriberRole": "{}".format(subscriber_role)}
+ }
+ policy_list.append(rest_client.request(json=policy_scope))
+ return policy_list, prov_status
+
+
+def remote_api(req_json, osdf_config, service_type="placement"):
+ """Make a request to policy and return response -- it accounts for multiple requests that be needed
+ :param req_json: policy request object (can have multiple policy names)
+ :param osdf_config: main config that will have credential information
+ :param service_type: the type of service to call: "placement", "scheduling"
+ :return: all related policies and provStatus retrieved from Subscriber policy
+ """
+# if not req_json[service_type + "Info"]['policyId']:
+# return []
+
+ config = osdf_config.deployment
+ uid, passwd = config['policyPlatformUsername'], config['policyPlatformPassword']
+ pcuid, pcpasswd = config['policyClientUsername'], config['policyClientPassword']
+ headers = {"ClientAuth": base64.b64encode(bytes("{}:{}".format(pcuid, pcpasswd), "ascii"))}
+ headers.update({'Environment': config['policyPlatformEnv']})
+ url = config['policyPlatformUrl']
+ rc = RestClient(userid=uid, passwd=passwd, headers=headers, url=url, log_func=debug_log.debug)
+
+ if osdf_config.core['policy_info'][service_type]['policy_fetch'] == "by_name":
+ policies = get_by_name(rc, req_json[service_type + "Info"]['policyId'], wildcards=True)
+ elif osdf_config.core['policy_info'][service_type]['policy_fetch'] == "by_name_no_wildcards":
+ policies = get_by_name(rc, req_json[service_type + "Info"]['policyId'], wildcards=False)
+ else: # Get policy by scope
+ policies, prov_status = get_by_scope(rc, req_json, osdf_config.core, service_type)
+
+ # policies in res are list of lists, so flatten them; also only keep config part
+ formatted_policies = []
+ for x in itertools.chain(*policies):
+ if x['config'] is None:
+ raise BusinessException("Config not found for policy with name %s" % x['policyName'])
+ else:
+ formatted_policies.append(json.loads(x['config']))
+ return formatted_policies, prov_status
+
+
+def local_policies_location(req_json, osdf_config, service_type):
+ """
+ Get folder and list of policy_files if "local policies" option is enabled
+ :param service_type: placement supported for now, but can be any other service
+ :return: a tuple (folder, file_list) or None
+ """
+ lp = osdf_config.core.get('osdf_hacks', {}).get('local_policies', {})
+ if lp.get('global_disabled'):
+ return None # short-circuit to disable all local policies
+ if lp.get('local_{}_policies_enabled'.format(service_type)):
+ if service_type == "scheduling":
+ return lp.get('{}_policy_dir'.format(service_type)), lp.get('{}_policy_files'.format(service_type))
+ else:
+ model_name = retrieve_node(req_json, osdf_config.core['policy_info'][service_type]['policy_scope']['service_name'])
+ service_name = data_mapping.get_service_type(model_name)
+ return lp.get('{}_policy_dir_{}'.format(service_type, service_name.lower())), lp.get('{}_policy_files_{}'.format(service_type, service_name.lower()))
+ return None
+
+
+def get_policies(request_json, service_type):
+ """Validate the request and get relevant policies
+ :param request_json: Request object
+ :param service_type: the type of service to call: "placement", "scheduling"
+ :return: policies associated with this request and provStatus retrieved from Subscriber policy
+ """
+ prov_status = []
+ req_info = request_json['requestInfo']
+ req_id = req_info['requestId']
+ metrics_log.info(MH.requesting("policy", req_id))
+ local_info = local_policies_location(request_json, osdf_config, service_type)
+
+ if local_info: # tuple containing location and list of files
+ to_filter = None
+ if osdf_config.core['policy_info'][service_type]['policy_fetch'] == "by_name":
+ to_filter = request_json[service_type + "Info"]['policyId']
+ policies = get_local_policies(local_info[0], local_info[1], to_filter)
+ else:
+ policies, prov_status= remote_api(request_json, osdf_config, service_type)
+
+ return policies, prov_status
diff --git a/osdf/adapters/policy/utils.py b/osdf/adapters/policy/utils.py
new file mode 100644
index 0000000..a006f12
--- /dev/null
+++ b/osdf/adapters/policy/utils.py
@@ -0,0 +1,58 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 collections import defaultdict
+
+
+def group_policies(flat_policies):
+ """Filter policies using the following steps:
+ 1. Apply prioritization among the policies that are sharing the same policy type and resource type
+ 2. Remove redundant policies that may applicable across different types of resource
+ 3. Filter policies based on type and return
+ :param flat_policies: list of flat policies
+ :return: Filtered policies
+ """
+ aggregated_policies = {}
+ filter_policies = defaultdict(list)
+ policy_name = []
+ for policy in flat_policies:
+ policy_type = policy['content']['type']
+ if policy_type not in aggregated_policies:
+ aggregated_policies[policy_type] = defaultdict(list)
+ for resource in policy['content']['policyScope']['resourceInstanceType']:
+ aggregated_policies[policy_type][resource].append(policy)
+ for policy_type in aggregated_policies:
+ for resource in aggregated_policies[policy_type]:
+ if len(aggregated_policies[policy_type][resource]) > 0:
+ aggregated_policies[policy_type][resource].sort(key=lambda x: x['priority'], reverse=True)
+ policy = aggregated_policies[policy_type][resource][0]
+ if policy['policyName'] not in policy_name:
+ filter_policies[policy['content']['type']].append(policy)
+ policy_name.append(policy['policyName'])
+ return filter_policies
+
+
+def _regex_policy_name(policy_name):
+ """Get the correct policy name as a regex
+ (e.g. OOF_HAS_vCPE.cloudAttributePolicy ends up in policy as OOF_HAS_vCPE.Config_MS_cloudAttributePolicy.1.xml
+ So, for now, we query it as OOF_HAS_vCPE..*aicAttributePolicy.*)
+ :param policy_name: Example: OOF_HAS_vCPE.aicAttributePolicy
+ :return: regexp for policy: Example: OOF_HAS_vCPE..*aicAttributePolicy.*
+ """
+ p = policy_name.partition('.')
+ return p[0] + p[1] + ".*" + p[2] + ".*"
diff --git a/osdf/adapters/request_parsing/__init__.py b/osdf/adapters/request_parsing/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/adapters/request_parsing/__init__.py
diff --git a/osdf/adapters/request_parsing/placement.py b/osdf/adapters/request_parsing/placement.py
new file mode 100644
index 0000000..d7a6575
--- /dev/null
+++ b/osdf/adapters/request_parsing/placement.py
@@ -0,0 +1,33 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 copy
+import json
+from osdf.utils.programming_utils import list_flatten, dot_notation
+
+
+def json_path_after_expansion(req_json, reference):
+ """
+ Get the child node(s) from the dot-notation [reference] and parent [req_json].
+ For placement and other requests, there are encoded JSONs inside the request or policy,
+ so we need to expand it and then do a search over the parent plus expanded JSON.
+ """
+ req_json_copy = copy.deepcopy(req_json) # since we expand the JSON in place, we work on a copy
+ req_json_copy['placementInfo']['orderInfo'] = json.loads(req_json_copy['placementInfo']['orderInfo'])
+ info = dot_notation(req_json_copy, reference)
+ return list_flatten(info) if isinstance(info, list) else info
diff --git a/osdf/adapters/sdc/__init__.py b/osdf/adapters/sdc/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/adapters/sdc/__init__.py
diff --git a/osdf/adapters/sdc/asdc.py b/osdf/adapters/sdc/asdc.py
new file mode 100755
index 0000000..43932ba
--- /dev/null
+++ b/osdf/adapters/sdc/asdc.py
@@ -0,0 +1,40 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 osdf.utils.interfaces import RestClient
+import xml.etree.ElementTree as ET
+
+def request(model_version_id, request_id, config):
+ """Get all of the license artifacts from SDC using service_resource_id and model_version_id
+ :param model_version_id: model_version_id
+ :param request_id: request_id
+ :return: license artifacts from SDC
+ """
+ base_url = config['sdcUrl']
+ uid, passwd = config['sdcUsername'], config['sdcPassword']
+ headers = {"CSP_UID": config['sdcMechId'], "X-ONAP-InstanceID": "osdf"}
+ rc = RestClient(userid=uid, passwd=passwd, headers=headers, method="GET", req_id=request_id)
+ resource_data = rc.request(base_url + '/resources/{}/metadata'.format(model_version_id))
+
+ artifact_ids = [x['artifactURL'].split("/resources/")[-1] # get the part after /resources/
+ for x in resource_data.get('artifacts', []) if x.get('artifactType') == "VF_LICENSE"]
+ artifact_urls = [base_url + '/resources/' + str(artifact_id) for artifact_id in artifact_ids]
+ licenses = []
+ for x in artifact_urls:
+ licenses.append(ET.fromstring(rc.request(x, asjson=False)))
+ return licenses
diff --git a/osdf/adapters/sdc/constraint_handler.py b/osdf/adapters/sdc/constraint_handler.py
new file mode 100644
index 0000000..2aae9a0
--- /dev/null
+++ b/osdf/adapters/sdc/constraint_handler.py
@@ -0,0 +1,81 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 osdf.config.base import osdf_config
+from osdf.utils.programming_utils import dot_notation
+
+ns = {'p': 'http://xmlns.onap.org/sdc/license-model/1.0'}
+config_local = osdf_config.core
+
+
+def choose_license(license_artifacts, order_info, service_type):
+ entitlement_pool_uuids = []
+ license_key_group_uuids = []
+
+ for license_artifact in license_artifacts:
+ for feature in license_artifact.findall('./p:feature-group-list/', ns):
+ for entitlement in feature.findall('./p:entitlement-pool-list/', ns):
+ if is_valid(entitlement, order_info, service_type):
+ entitlement_pool_uuid = entitlement.find('p:entitlement-pool-uuid', ns).text
+ entitlement_pool_uuids.append(entitlement_pool_uuid)
+ for license_key_group in feature.findall('./p:license-key-group-list/', ns):
+ if is_valid(license_key_group, order_info, service_type):
+ license_key_group_uuid = license_key_group.find('p:license-key-group-uuid', ns).text
+ license_key_group_uuids.append(license_key_group_uuid)
+ return entitlement_pool_uuids, license_key_group_uuids
+
+
+# element is expected to be a license-key-group or entitlement-pool
+# if these elements diverge at a later date this method should be refactored
+def is_valid(element, order_info, service_type):
+ for limit in element.findall('./p:sp-limits/p:limit', ns):
+ # description = limit.find('p:description', ns).text
+ metric_value = limit.find('p:values', ns).text
+ metric = limit.find('p:metric', ns).text
+ try:
+ order_value = dot_notation(order_info, config_local['service_info'][service_type][metric])
+ # print("The order has the value %s for the metric %s and the limit specifies the value %s. The limit has the description %s." % (order_value, metric, metric_value, description))
+ if isinstance(order_value, list): # it is possible a list is returned, for example a list of vnfs for vCPE
+ for arr_value in order_value:
+ if str(metric_value) != str(arr_value):
+ return False
+ else:
+ if str(metric_value) != str(order_value):
+ return False
+ except KeyError:
+ return False
+ # vendor limits
+ for limit in element.findall('./p:vendor-limits/p:limit', ns):
+ # description = limit.find('p:description', ns).text
+ metric_value = limit.find('p:values', ns).text
+ metric = limit.find('p:metric', ns).text
+ try:
+ order_value = dot_notation(order_info, config_local['service_info'][service_type][metric])
+ if isinstance(order_value, list): # it is possible a list is returned, for example a list of vnfs for vCPE
+ for arr_value in order_value:
+ if str(metric_value) != str(arr_value):
+ return False
+ else:
+ if str(metric_value) != str(order_value):
+ return False
+ # print("The order has the value %s for the metric %s and the limit specifies the value %s. The limit has the description %s." % (order_value, metric, metric_value, description))
+
+ except KeyError:
+ return False
+ return True
+
diff --git a/osdf/config/__init__.py b/osdf/config/__init__.py
new file mode 100644
index 0000000..303a8ce
--- /dev/null
+++ b/osdf/config/__init__.py
@@ -0,0 +1,32 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 yaml
+import json
+
+from osdf.utils.programming_utils import MetaSingleton
+
+
+class CoreConfig(metaclass=MetaSingleton):
+ core_config = None
+
+ def get_core_config(self, config_file=None):
+ if self.core_config is None:
+ self.core_config = yaml.load(open(config_file))
+ return self.core_config
+
diff --git a/osdf/config/base.py b/osdf/config/base.py
new file mode 100644
index 0000000..b8aacff
--- /dev/null
+++ b/osdf/config/base.py
@@ -0,0 +1,36 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 os
+
+import osdf.config.loader as config_loader
+import osdf.config.credentials as creds
+from osdf.utils.programming_utils import DotDict
+
+config_spec = {
+ "deployment": os.environ.get("OSDF_MANAGER_CONFIG_FILE", "config/osdf_config.yaml"),
+ "core": "config/common_config.yaml"
+ }
+
+osdf_config = DotDict(config_loader.all_configs(**config_spec))
+
+http_basic_auth_credentials = creds.load_credentials(osdf_config)
+
+dmaap_creds = creds.dmaap_creds()
+
+creds_prefixes = {"so": "so", "cm": "cmPortal"}
diff --git a/osdf/config/credentials.py b/osdf/config/credentials.py
new file mode 100644
index 0000000..e5a6399
--- /dev/null
+++ b/osdf/config/credentials.py
@@ -0,0 +1,60 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 osdf import auth_groups, userid_suffix, passwd_suffix
+
+
+def dmaap_creds(dmaap_file="/etc/dcae/dmaap.conf"):
+ """Get DMaaP credentials from DCAE for publish and subscribe"""
+ try:
+ dmaap_creds = _get_dmaap_creds(dmaap_file)
+ except:
+ dmaap_creds = {}
+ return dmaap_creds
+
+
+def _get_dmaap_creds(dmaap_file):
+ """Get DMaaP credentials from DCAE for publish and subscribe"""
+ streams = json.load(open(dmaap_file, 'r'))
+ pubs = [x for x in streams
+ if x['dmaapStreamId'] == 'requests' and x['dmaapAction'] == 'publish']
+ subs = [x for x in streams
+ if x['dmaapStreamId'] == 'responses' and x['dmaapAction'] == 'subscribe']
+
+ def get_dmaap_info(x):
+ """Get DMaaP credentials from dmaap_object 'x'"""
+ return dict(url=x.get('dmaapUrl'), userid=x.get('dmaapUserName'), passwd=x.get('dmaapPassword'))
+
+ return {'pub': get_dmaap_info(pubs[0]), 'sub': get_dmaap_info(subs[0])}
+
+
+def load_credentials(osdf_config):
+ """Get credentials as dictionaries grouped by auth_group (e.g. creds["Placement"]["user1"] = "pass1")"""
+ creds = dict((x, dict()) for x in auth_groups) # each auth group has userid, passwd dict
+ suffix_start = len(userid_suffix)
+
+ config = osdf_config.deployment
+
+ for element, username in config.items():
+ for x in auth_groups:
+ if element.startswith("osdf" + x) and element.endswith(userid_suffix):
+ passwd = config[element[:-suffix_start] + passwd_suffix]
+ creds[x][username] = passwd
+ return creds
diff --git a/osdf/config/loader.py b/osdf/config/loader.py
new file mode 100644
index 0000000..7cb363a
--- /dev/null
+++ b/osdf/config/loader.py
@@ -0,0 +1,51 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 yaml
+
+
+def load_config_file(config_file: str, child_name="dockerConfiguration") -> dict:
+ """
+ Load OSDF configuration from a file -- currently only yaml/json are supported
+ :param config_file: path to config file (.yaml or .json).
+ :param child_name: if present, return only that child node
+ :return: config (all or specific child node)
+ """
+ with open(config_file, 'r') as fid:
+ res = {}
+ if config_file.endswith(".yaml"):
+ res = yaml.load(fid)
+ elif config_file.endswith(".json") or config_file.endswith("json"):
+ res = json.load(fid)
+ return res.get(child_name, res) if child_name else res
+
+
+def dcae_config(config_file: str) -> dict:
+ return load_config_file(config_file, child_name="dockerConfiguration")
+
+
+def all_configs(**kwargs: dict) -> dict:
+ """
+ Load all specified configurations
+ :param config_file_spec: key-value pairs
+ (e.g. { "core": "common_config.yaml", "deployment": "/tmp/1452523532json" })
+ :return: merged config as a nested dictionary
+ """
+ return {k: load_config_file(fname) for k, fname in kwargs.items()}
diff --git a/osdf/logging/__init__.py b/osdf/logging/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/logging/__init__.py
diff --git a/osdf/logging/osdf_logging.py b/osdf/logging/osdf_logging.py
new file mode 100755
index 0000000..9a6ff4e
--- /dev/null
+++ b/osdf/logging/osdf_logging.py
@@ -0,0 +1,229 @@
+import logging
+import traceback
+import uuid
+
+import logging
+from logging.handlers import RotatingFileHandler
+from osdf.utils.programming_utils import MetaSingleton
+
+
+def log_handlers_pre_onap(config_file="config/pre_onap_logging_common_v1.config",
+ service_name="OOF_OSDF"):
+ """
+ Convenience handlers for logging to different log files
+
+ :param config_file: configuration file (properties file) that specifies log location, rotation, etc.
+ :param service_name: name for this service
+ :return: dictionary of log objects: "error", "metrics", "audit", "debug"
+
+ We can use the returned values as follows:
+ X["error"].fatal("a FATAL message for the error log")
+ X["error"].error("an ERROR message for the error log")
+ X["error"].warn("a WARN message for the error log")
+ X["audit"].info("an INFO message for the audit log")
+ X["metrics"].info("an INFO message for the metrics log")
+ X["debug"].debug("a DEBUG message for the debug log")
+ """
+ # Keeping main_params as a place-holder for ONAP related logging needs
+ # main_params = dict(instanceUUID=uuid.uuid1(), serviceName=service_name, configFile=config_file)
+ return dict((x, logging.getLogger(x)) # keep **main_params as a placeholder for ONAP fields
+ for x in ["error", "metrics", "audit", "debug"])
+
+
+def format_exception(err, prefix=None):
+ """Format operation for use with ecomp logging
+ :param err: exception object
+ :param prefix: prefix string message
+ :return: formatted exception (via repr(traceback.format_tb(err.__traceback__))
+ """
+ exception_lines = traceback.format_exception(err.__class__, err, err.__traceback__)
+ exception_desc = "".join(exception_lines)
+ return exception_desc if not prefix else prefix + ": " + exception_desc
+
+
+class OOF_OSDFLogMessageHelper(metaclass=MetaSingleton):
+ """Provides loggers as a singleton (otherwise, we end up with duplicate messages).
+ Provides error_log, metric_log, audit_log, and debug_log (in that order)
+ Additionally can provide specific log handlers too
+ """
+ log_handlers = None
+ default_levels = ["error", "metrics", "audit", "debug"]
+
+ def _setup_handlers(self, log_version="pre_onap", config_file=None, service_name=None):
+ """return error_log, metrics_log, audit_log, debug_log"""
+ if self.log_handlers is None:
+ params = {}
+ params.update({"config_file": config_file} if config_file else {})
+ params.update({"service_name": service_name} if service_name else {})
+
+ if log_version == "pre_onap":
+ self.log_handlers = log_handlers_pre_onap(**params)
+
+ def get_handlers(self, levels=None, log_version="pre_onap", config_file=None, service_name=None):
+ """Return ONAP-compliant log handlers for different levels. Each "level" ends up in a different log file
+ with a prefix of that level.
+
+ For example: error_log, metrics_log, audit_log, debug_log in that order
+ :param levels: None or list of levels subset of self.default_levels (["error", "metrics", "audit", "debug"])
+ :param log_version: Currently only pre_onap is supported
+ :param config_file: Logging configuration file for ONAP compliant logging
+ :param service_name: Name of the service
+ :return: list of log_handlers in the order of levels requested.
+ if levels is None: we return handlers for self.default_levels
+ if levels is ["error", "audit"], we return log handlers for that.
+ """
+ self._setup_handlers(log_version="pre_onap", config_file=config_file, service_name=service_name)
+ wanted_levels = self.default_levels if levels is None else levels
+ return [self.log_handlers.get(x) for x in wanted_levels]
+
+
+class OOF_OSDFLogMessageFormatter(object):
+
+ @staticmethod
+ def accepted_valid_request(req_id, request):
+ return "Accepted valid request for ID: {} for endpoint: {}".format(
+ req_id, request.url)
+
+ @staticmethod
+ def invalid_request(req_id, err):
+ return "Invalid request for request ID: {}; cause: {}".format(
+ req_id, format_exception(err))
+
+ @staticmethod
+ def invalid_response(req_id, err):
+ return "Invalid response for request ID: {}; cause: {}".format(
+ req_id, format_exception(err))
+
+ @staticmethod
+ def malformed_request(request, err):
+ return "Malformed request for URL {}, from {}; cause: {}".format(
+ request.url, request.remote_address, format_exception(err))
+
+ @staticmethod
+ def malformed_response(response, client, err):
+ return "Malformed response {} for client {}; cause: {}".format(
+ response, client, format_exception(err))
+
+ @staticmethod
+ def need_policies(req_id):
+ return "Policies required but found no policies for request ID: {}".format(req_id)
+
+ @staticmethod
+ def policy_service_error(url, req_id, err):
+ return "Unable to call policy for {} for request ID: {}; cause: {}".format(
+ url, req_id, format_exception(err))
+
+ @staticmethod
+ def requesting_url(url, req_id):
+ return "Making a call to URL {} for request ID: {}".format(url, req_id)
+
+ @staticmethod
+ def requesting(service_name, req_id):
+ return "Making a call to service {} for request ID: {}".format(service_name, req_id)
+
+ @staticmethod
+ def error_requesting(service_name, req_id, err):
+ return "Error while requesting service {} for request ID: {}; cause: {}".format(
+ service_name, req_id, format_exception(err))
+
+ @staticmethod
+ def calling_back(req_id, callback_url):
+ return "Posting result to callback URL for request ID: {}; callback URL={}".format(
+ req_id, callback_url)
+
+ @staticmethod
+ def calling_back_with_body(req_id, callback_url, body):
+ return "Posting result to callback URL for request ID: {}; callback URL={} body={}".format(
+ req_id, callback_url, body)
+
+ @staticmethod
+ def error_calling_back(req_id, callback_url, err):
+ return "Error while posting result to callback URL {} for request ID: {}; cause: {}".format(
+ req_id, callback_url, format_exception(err))
+
+ @staticmethod
+ def received_request(url, remote_addr, json_body):
+ return "Received a call to {} from {} {}".format(url, remote_addr, json_body)
+
+ @staticmethod
+ def new_worker_thread(req_id, extra_msg=""):
+ res = "Initiating new worker thread for request ID: {}".format(req_id)
+ return res + extra_msg
+
+ @staticmethod
+ def inside_worker_thread(req_id, extra_msg=""):
+ res = "Inside worker thread for request ID: {}".format(req_id)
+ return res + extra_msg
+
+ @staticmethod
+ def processing(req_id, desc):
+ return "Processing request ID: {} -- {}".format(req_id, desc)
+
+ @staticmethod
+ def processed(req_id, desc):
+ return "Processed request ID: {} -- {}".format(req_id, desc)
+
+ @staticmethod
+ def error_while_processing(req_id, desc, err):
+ return "Error while processing request ID: {} -- {}; cause: {}".format(
+ req_id, desc, format_exception(err))
+
+ @staticmethod
+ def creating_local_env(req_id):
+ return "Creating local environment request ID: {}".format(
+ req_id)
+
+ @staticmethod
+ def error_local_env(req_id, desc, err):
+ return "Error while creating local environment for request ID: {} -- {}; cause: {}".format(
+ req_id, desc, err.__traceback__)
+
+ @staticmethod
+ def inside_new_thread(req_id, extra_msg=""):
+ res = "Spinning up a new thread for request ID: {}".format(req_id)
+ return res + " " + extra_msg
+
+ @staticmethod
+ def error_response_posting(req_id, desc, err):
+ return "Error while posting a response for a request ID: {} -- {}; cause: {}".format(
+ req_id, desc, err.__traceback__)
+
+ @staticmethod
+ def received_http_response(resp):
+ return "Received response [code: {}, headers: {}, data: {}]".format(
+ resp.status_code, resp.headers, resp.__dict__)
+
+ @staticmethod
+ def sending_response(req_id, desc):
+ return "Response is sent for request ID: {} -- {}".format(
+ req_id, desc)
+
+ @staticmethod
+ def listening_response(req_id, desc):
+ return "Response is sent for request ID: {} -- {}".format(
+ req_id, desc)
+
+ @staticmethod
+ def items_received(item_num, item_type, desc="Received"):
+ return "{} {} {}".format(desc, item_num, item_type)
+
+ @staticmethod
+ def items_sent(item_num, item_type, desc="Published"):
+ return "{} {} {}".format(desc, item_num, item_type)
+
+
+MH = OOF_OSDFLogMessageFormatter
+error_log, metrics_log, audit_log, debug_log = OOF_OSDFLogMessageHelper().get_handlers()
+
+def warn_audit_error(msg):
+ """Log the message to error_log.warn and audit_log.warn"""
+ log_message_multi(msg, audit_log.warn, error_log.warn)
+
+
+def log_message_multi(msg, *logger_methods):
+ """Log the msg to multiple loggers
+ :param msg: message to log
+ :param logger_methods: e.g. error_log.warn, audit_log.warn, etc.
+ """
+ for log_method in logger_methods:
+ log_method(msg)
diff --git a/osdf/models/api/common.py b/osdf/models/api/common.py
new file mode 100755
index 0000000..0d2d0eb
--- /dev/null
+++ b/osdf/models/api/common.py
@@ -0,0 +1,54 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 datetime
+from pprint import pformat
+
+from dateutil.parser import parse
+from schematics.exceptions import ConversionError
+from schematics.models import Model
+from schematics.types import DateTimeType
+
+
+class OSDFModel(Model):
+ """Extends generic model with a couple of extra methods"""
+ def __str__(self):
+ """Return values of object's attributes -- excluding hidden or callable ones"""
+ def _str_format(x):
+ """Coerce as string for some special objects"""
+ return str(x) if isinstance(x, datetime.datetime) else x
+
+ z1 = dict((x, getattr(self, x)) for x in dir(self)
+ if not x.startswith("_") and not callable(getattr(self, x)))
+ z1 = dict((x, _str_format(y)) for x, y in z1.items())
+ return pformat(z1, depth=4, indent=2, width=1000)
+
+ def __repr__(self):
+ """Return values of object's attributes -- excluding hidden or callable ones"""
+ return self.__str__()
+
+
+class CustomISODateType(DateTimeType):
+ """Schematics doesn't support full ISO, so we use custom one"""
+ def to_native(self, value, context=None):
+ if isinstance(value, datetime.datetime):
+ return value
+ try:
+ return parse(value)
+ except:
+ raise ConversionError(u'Invalid timestamp {}'.format(value))
diff --git a/osdf/models/api/placementRequest.py b/osdf/models/api/placementRequest.py
new file mode 100644
index 0000000..73eac75
--- /dev/null
+++ b/osdf/models/api/placementRequest.py
@@ -0,0 +1,124 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 .common import OSDFModel
+from schematics.types import StringType, URLType, IntType, FloatType
+from schematics.types.compound import ModelType, ListType
+
+
+class RequestInfo(OSDFModel):
+ """Info for northbound request from client such as SO"""
+ transactionId = StringType(required=True)
+ requestId = StringType(required=True)
+ callbackUrl = URLType(required=True)
+ sourceId = StringType(required=True)
+ optimizer = ListType(StringType())
+ numSolutions = IntType()
+ timeout = IntType()
+ requestType = StringType()
+
+
+class CandidateInfo(OSDFModel):
+ """Preferred candidate for a resource (sent as part of a request from client)"""
+ candidateType = StringType(required=True)
+ candidates = ListType(StringType(required=True))
+
+
+class ResourceModelInfo(OSDFModel):
+ """Model information for a specific resource"""
+ modelCustomizationId = StringType(required=True)
+ modelInvariantId = StringType(required=True)
+ modelName = StringType()
+ modelVersion = StringType()
+ modelVersionId = StringType()
+ modelType = StringType()
+ operationalStatus = StringType()
+
+
+class ExistingLicenseInfo(OSDFModel):
+ entitlementPoolUUID = ListType(StringType())
+ licenseKeyGroupUUID = ListType(StringType())
+
+
+class LicenseDemand(OSDFModel):
+ resourceInstanceType = StringType(required=True)
+ serviceResourceId = StringType(required=True)
+ resourceModuleName = StringType(required=True)
+ resourceModelInfo = ModelType(ResourceModelInfo)
+ existingLicense = ModelType(ExistingLicenseInfo)
+
+
+class PlacementDemand(OSDFModel):
+ resourceInstanceType = StringType(required=True)
+ serviceResourceId = StringType(required=True)
+ resourceModuleName = StringType(required=True)
+ exclusionCandidateInfo = ListType(ModelType(CandidateInfo))
+ requiredCandidateInfo = ListType(ModelType(CandidateInfo))
+ resourceModelInfo = ModelType(ResourceModelInfo)
+ tenantId = StringType()
+ tenantName = StringType()
+
+
+class ExistingPlacementInfo(OSDFModel):
+ serviceInstanceId = StringType(required=True)
+
+
+class DemandInfo(OSDFModel):
+ """Requested resources (sent as part of a request from client)"""
+ placementDemand = ListType(ModelType(PlacementDemand))
+ licenseDemand = ListType(ModelType(LicenseDemand))
+
+
+class SubscriberInfo(OSDFModel):
+ """Details on the customer that subscribes to the VNFs"""
+ globalSubscriberId = StringType(required=True)
+ subscriberName = StringType()
+ subscriberCommonSiteId = StringType()
+
+
+class ServiceModelInfo(OSDFModel):
+ """ASDC Service model information"""
+ modelType = StringType(required=True)
+ modelInvariantId = StringType(required=True)
+ modelVersionId = StringType(required=True)
+ modelName = StringType(required=True)
+ modelVersion = StringType(required=True)
+
+
+class Location(OSDFModel):
+ latitude = FloatType(required=True)
+ longitude = FloatType(required=True)
+
+
+class PlacementInfo(OSDFModel):
+ """Information specific to placement optimization"""
+ serviceModelInfo = ModelType(ServiceModelInfo)
+ subscriberInfo = ModelType(SubscriberInfo)
+ demandInfo = ModelType(DemandInfo, required=True)
+ orderInfo = StringType()
+ policyId = ListType(StringType())
+ serviceInstanceId = StringType()
+ existingPlacement = ModelType(ExistingPlacementInfo)
+ location = ModelType(Location)
+ serviceType = StringType()
+
+
+class PlacementAPI(OSDFModel):
+ """Request for placement optimization (specific to optimization and additional metadata"""
+ requestInfo = ModelType(RequestInfo, required=True)
+ placementInfo = ModelType(PlacementInfo, required=True)
diff --git a/osdf/models/api/placementResponse.py b/osdf/models/api/placementResponse.py
new file mode 100644
index 0000000..e9746d6
--- /dev/null
+++ b/osdf/models/api/placementResponse.py
@@ -0,0 +1,57 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 .common import OSDFModel
+from schematics.types import StringType
+from schematics.types.compound import ModelType, ListType
+
+
+# TODO: update osdf.models
+
+class LicenseSolution(OSDFModel):
+ serviceResourceId = StringType(required=True)
+ resourceModuleName = StringType(required=True)
+ entitlementPoolList = ListType(StringType(required=True))
+ licenseKeyGroupList = ListType(StringType(required=True))
+
+
+class AssignmentInfo(OSDFModel):
+ variableName = StringType(required=True)
+ variableValue = StringType(required=True)
+
+
+class PlacementSolution(OSDFModel):
+ serviceResourceId = StringType(required=True)
+ resourceModuleName = StringType(required=True)
+ inventoryType = StringType(required=True)
+ serviceInstanceId = StringType()
+ cloudRegionId = StringType()
+ assignmentInfo = ListType(ModelType(AssignmentInfo))
+
+
+class SolutionInfo(OSDFModel):
+ placement = ListType(ModelType(PlacementSolution), min_size=1)
+ license = ListType(ModelType(LicenseSolution), min_size=1)
+
+
+class PlacementResponse(OSDFModel):
+ transactionId = StringType(required=True)
+ requestId = StringType(required=True)
+ requestState = StringType(required=True)
+ statusMessage = StringType(required=True)
+ solutionInfo = ModelType(SolutionInfo)
diff --git a/osdf/models/policy/cmso/xacml/placementPolicies.xcore b/osdf/models/policy/cmso/xacml/placementPolicies.xcore
new file mode 100644
index 0000000..3348cb0
--- /dev/null
+++ b/osdf/models/policy/cmso/xacml/placementPolicies.xcore
@@ -0,0 +1,718 @@
+/*
+ * This XCORE file contains models for the placement optimization policies in SNIRO.
+ * @author Ankitkumar Patel
+ * @version 0.0.1
+ * @since 2017-04-13
+ */
+
+package com.att.ecomp.sniro.policies.placement
+
+import java.util.UUID
+
+annotation "http://ecomp.att.com" as ecomp
+annotation "http://ecomp.att.com/policy" as policy
+
+type UUID wraps UUID
+
+/*
+ * Comparison operators
+ */
+enum ComparisonOperator{
+ less=1, grater=2, lessEqual=3, greaterEqual=4, equal=5, notEqual=6
+}
+
+enum TenantType{
+ GW_TENANT_ID=1, PORTAL_TENANT_ID=2
+}
+
+/*
+ * Computational operators
+ */
+enum ComputationalOperator{
+ sum=1, product=2
+}
+
+
+/*
+ * Qualifier types
+ */
+enum Qualifier{
+ same=1, different=2
+}
+
+/*
+ * Zone category
+ */
+enum ZoneCategory{
+ disaster=1, region=2, complex=3, time=4, maintenance=5
+}
+
+/*
+ * Geographical region types
+ */
+enum GeoRegion{
+ US=1, EMEA=2, AP=3, CALA=4, CA=5, INTERNATIONAL=6
+}
+
+/*
+ * Parameters
+ */
+enum Parameter{
+ distance=0, latency=1, aic_version=2
+ //thoughput=1, geoDistance=1, airDistance=2, latency=3, bandwidth=4
+}
+
+
+/*
+ * The type of inventory defined in AIC
+ */
+enum InventoryType{
+ service=1, cloud=2
+}
+
+/*
+ * The type of AT&T network
+ */
+ enum NetworkType{
+ ip=1
+ }
+
+/*
+ * Objective functions.
+ */
+
+enum ObjectiveFunction{
+ minimize=1, maximize=2
+}
+
+/*
+ * This is a model of a condition.
+ * @param parameter This is the parameter of interest.
+ * @param operator This is a comparison operator.
+ * @param value This is a value of a parameter
+ */
+class ConditionalInfo{
+ @ecomp(^type = "configuration")
+ Parameter parameter
+ @ecomp(^type = "configuration")
+ ComparisonOperator operator
+ @ecomp(^type = "configuration")
+ String value
+}
+
+enum LocationInfo{
+ customer_loc=1, none=2, customer_pref_loc=3
+}
+
+
+/*
+ * Model for distance to location property.
+ * @param distanceCondition This is a distance condition.
+ * @param locationInfo This is a location with respect to which distance condition is applied.
+ */
+class DistanceToLocationProperty{
+ //distanceCondition.parameter must be distance.
+ @ecomp(^type = "configuration")
+ contains ConditionalInfo distanceCondition
+ @ecomp(^type = "configuration")
+ LocationInfo locationInfo
+}
+
+enum DistanceToLocationPolicyType{
+ distance_to_location=1
+}
+
+/*
+ * Model for distance to location policy.
+ * @param identity This is an identity created by a user.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resource instances over which this policy is applied.
+ * @param distanceToLocationProperty This is the distance properties of the policy.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class DistanceToLocationPolicy extends SniroPolicyMetaInfo{
+ @ecomp(^type = "configuration")
+ @policy (matching = "true")
+ DistanceToLocationPolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains DistanceToLocationProperty distanceToLocationProperty
+}
+
+/*
+ * The property associated with the NetworkBetweenDemandsPolicy.
+ * @param latencyCondition The latency property associated with the policy.
+ * @param locationInfo The customer location information.
+ */
+class NetworkToLocationProperty{
+ //latencyCondition.parameter must be latency.
+ @ecomp(^type = "configuration")
+ contains ConditionalInfo latencyCondition
+ @ecomp(^type = "configuration")
+ LocationInfo locationInfo
+}
+
+enum NetworkToLocationPolicyType{
+ network_to_location=1
+}
+
+/*
+ * Network between demands policy.
+ * @param type The type of a policy.
+ * @param resourceInstanceType The resources associated with a policy.
+ * @param networkToLocationProperty The property associated with the policy.
+ */
+
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class NetworkToLocationPolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ NetworkToLocationPolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains NetworkToLocationProperty networkToLocationProperty
+}
+
+/*
+ * The property associated with the NetworkBetweenDemandsPolicy.
+ * @param latencyCondition The latency property associated with the policy.
+ */
+class NetworkBetweenDemandsProperty{
+ //latencyCondition.parameter must be latency.
+ @ecomp(^type = "configuration")
+ contains ConditionalInfo latencyCondition
+}
+
+enum NetworkBetweenDemandsPolicyType{
+ network_between_demands=1
+}
+
+/*
+ * Network between demands policy.
+ * @param type The type of a policy.
+ * @param resourceInstanceType The resources associated with a policy.
+ * @param networkBetweenDemandsProperty The property associated with the policy.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class NetworkBetweenDemandsPolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ NetworkBetweenDemandsPolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains NetworkBetweenDemandsProperty networkBetweenDemandsProperty
+}
+
+
+/*
+ * Network roles supported a VNF placement
+ * @param all A list of network roles.
+ */
+class NetworkRoles{
+ @ecomp(^type = "configuration")
+ String[] all
+}
+
+/*
+ * Complex names supported by a VNF placement
+ * @param any A list of complex names.
+ */
+class Complex{
+ @ecomp(^type = "configuration")
+ String[] any
+}
+/*
+ * This are the AIC properties.
+ * @param aicVersion This is the version of AIC.
+ * @param aicType This is the type of AIC.
+ * @param dataPlace This is the type of data plane.
+ * @param hypervisor This is the type of hypervisor.
+ * @param networkRoles This is a list of connected networks.
+ * @param exclusivityGroups This is an exclusivity group Id
+ * @param state State in which a VNF should be located.
+ * @param country Country in which a VNF should be located.
+ * @param getRegion Geographical region in which a VNF should be located.
+ */
+class AicAttributeProperty{
+ //aicVersionCondition.parameter must be aicVersion.
+ @ecomp(^type = "configuration")
+ String aicVersion
+ @ecomp(^type = "configuration")
+ String aicType
+ @ecomp(^type = "configuration")
+ String dataPlane
+ @ecomp(^type = "configuration")
+ String hypervisor
+ @ecomp(^type = "configuration")
+ contains NetworkRoles networkRoles
+ @ecomp(^type = "configuration")
+ contains Complex complex
+ @ecomp(^type = "configuration")
+ String exclusivityGroups
+ @ecomp(^type = "configuration")
+ String state
+ @ecomp(^type = "configuration")
+ String country
+ @ecomp(^type = "configuration")
+ GeoRegion geoRegion
+ @ecomp(^type = "configuration")
+ String replicationRole
+
+}
+
+enum AicAttributePolicyType{
+ attribute=1
+}
+
+/*
+ * Model for the AIC attribute policy.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources over which the policy is applied.
+ * @param aicAttributeProperty This is the properties associated with the policy.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class AicAttributePolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ AicAttributePolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains AicAttributeProperty aicAttributeProperty
+}
+
+/*
+ * The property associated with the capacity policy.
+ * @param tenant The tenant whose capacity needs to be checked.
+ * @param description The location of a heat template.
+ */
+class CapacityProperty{
+ @ecomp(^type = "configuration")
+ TenantType tenant
+ @ecomp(^type = "configuration")
+ String description
+}
+
+enum CapacityPolicyType{
+ cloud_capacity=1
+}
+
+/*
+ * Capacity policy
+ * @param type The type of a policy.
+ * @param resourceInstanceType The type of resources associated with a policy.
+ * @param capacityProperty The property associated with a policy.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class CapacityPolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ CapacityPolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains CapacityProperty capacityProperty
+}
+
+enum InventoryGroupPolicyType{
+ inventory_group = 1
+}
+
+/*
+ * Model for the inventory group policy.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources that must be grouped/paired
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class InventoryGroupPolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ InventoryGroupPolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+}
+
+/*
+ * This is the property associated with this policy.
+ * @param controller ECOMP controller.
+ * @param request This should be key-value pairs to be sent in a request.
+ */
+class ResourceInstanceProperty{
+ @ecomp(^type = "configuration")
+ String controller
+ @ecomp(^type = "configuration")
+ String request
+}
+
+enum ResourceInstancePolicyType{
+ instance_fit=1
+}
+/*
+ * Model for the resource instance policy.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources.
+ * @param resourceInstanceProperty This is a property associated with each resource in the list.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class ResourceInstancePolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ ResourceInstancePolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains ResourceInstanceProperty resourceInstanceProperty
+
+}
+
+/*
+ * This is the property associated with this policy.
+ * @param controller ECOMP controller
+ * @param request This should be key-value pairs to be sent in a request.
+ */
+class ResourceRegionProperty{
+ @ecomp(^type = "configuration")
+ String controller
+ @ecomp(^type = "configuration")
+ String request
+}
+
+enum ResourceRegionPolicyType{
+ region_fit=1
+}
+
+/*
+ * Model for the resource region policy
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources.
+ * @param resourceRegionProperty This is a property associated with this policy.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class ResourceRegionPolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ ResourceRegionPolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains ResourceRegionProperty resourceRegionProperty
+}
+
+/*
+ * This is the property associated with zone policy.
+ * @param qualifier This is the qualifier.
+ * @param category This is the category of a zone.
+ */
+class ZoneProperty{
+ @ecomp(^type = "configuration")
+ Qualifier qualifier
+ @ecomp(^type = "configuration")
+ ZoneCategory category
+}
+
+enum ZonePolicyType{
+ zone=1
+}
+
+/*
+ * Model of the zone policy.
+ * @param type This is the type of a policy.
+ * @param resourceInstanceType This is a list of resources.
+ * @param zoneProperty This is the property associated with the policy.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+
+class ZonePolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ ZonePolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains ZoneProperty zoneProperty
+}
+
+/*
+ * The property associated with a VNF type.
+ * @param inventoryProvider The ECOMP entity providing inventory information.
+ * @param inventoryType The type of an inventory.
+ * @param serviceId The id of a service.
+ */
+class VNFPolicyProperty{
+ @ecomp(^type = "configuration")
+ String inventoryProvider
+ @ecomp(^type = "configuration")
+ InventoryType inventoryType
+ @ecomp(^type = "configuration")
+ contains Attributes attributes
+}
+
+/*
+ * The property associated with a Subscriber type.
+ * @param subscriberName The name of a subscriber.
+ * @param subscriberRole The role of a subscriber.
+ * @param provStatus The provisioning status of a subscriber.
+ */
+class SubscriberPolicyProperty{
+ @ecomp(^type = "configuration")
+ String[] subscriberName
+ @ecomp(^type = "configuration")
+ String[] subscriberRole
+ @ecomp(^type = "configuration")
+ String[] provStatus
+}
+
+enum VNFPolicyType{
+ vnfPolicy=1
+}
+
+enum SubscriberPolicyType{
+ subscriberPolicy=1
+}
+
+class Attributes{
+ @ecomp(^type = "configuration")
+ String globalCustomerId
+ @ecomp(^type = "configuration")
+ String operationalStatus
+ @ecomp(^type = "configuration")
+ String[] orchestrationStatus
+ @ecomp(^type = "configuration")
+ String modelInvariantId
+ @ecomp(^type = "configuration")
+ String modelVersionId
+ @ecomp(^type = "configuration")
+ String equipmentRole
+}
+
+/*
+ * Policy associated with a VNF.
+ * @param resourceInstance This parameter identifies a specific VNF.
+ * @param inventoryProvider This is the provider of inventory.
+ * @param inventoryType This is the type of inventory.
+ * @param serviceType The service associated with a VNF.
+ * @param serviceId The Id associated with a service.
+ * @param globalCustomerId The global id of a customer.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class VNFPolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ VNFPolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains VNFPolicyProperty[] property
+}
+
+/*
+ * Policy associated with a Subscriber.
+ * @param subscriberName The name of a subscriber.
+ * @param subscriberRole The role of a subscriber.
+ * @param provStatus The provisioning status of a subscriber.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class SubscriberPolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ SubscriberPolicyType ^type
+ @ecomp(^type = "configuration")
+ contains SubscriberPolicyProperty[] property
+}
+
+
+/*
+ * This is the property associated with this policy.
+ * @param providerUrl This is the url of provider to check the capacity.
+ * @param request This should be key-value pairs to be sent in a request.
+ */
+class InstanceReservationProperty{
+ @ecomp(^type = "configuration")
+ String controller
+ @ecomp(^type = "configuration")
+ String request
+}
+
+enum InstanceReservationPolicyType{
+ instance_reservation=1
+}
+/*
+ * Model for the resource instance policy.
+ * @param identity This is an identity created by a user.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources.
+ * @param resourceInstanceProperty This is a property associated with each resource in the list.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+class instanceReservationPolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ InstanceReservationPolicyType ^type
+ @ecomp(^type = "configuration")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ contains InstanceReservationProperty instanceReservationProperty
+
+}
+
+/*
+ * This is a model of an operand.
+ * @param parameter This is a parameter.
+ * @param associativity This is a list of entities with which a parameter is associated.
+ */
+ /*
+class Operand{
+ @ecomp(^type = "configuration")
+ Parameter parameter
+ @ecomp(^type = "configuration")
+ Entity associativity
+}
+*/
+
+/*
+ * This is the optimization function.
+ * @param identity This is an identity of a function.
+ * @param operation This is a computational operator.
+ * @param leftOperand This is a left operand of a function.
+ * @param rightOperand This is a right operand of a function.
+ */
+/*
+class OptimizationFunction{
+ @ecomp(^type = "configuration")
+ ExpressionIdentity identity
+ @ecomp(^type = "configuration")
+ ComputationalOperator operation
+ @ecomp(^type = "configuration")
+ contains Operand[] operands
+}
+*/
+
+/*
+ * Properties associated with a sub-expression.
+ * @param weight The weight of an expression.
+ * @param parameter The parameter involved in an expression.
+ * @param entity The entities involved in an expression.
+ * @param operator The operator of an expression.
+ * @param customerLocationInfo The location of a customer.
+ */
+class AttributeProperty{
+ @ecomp(^type = "configuration")
+ double weight
+ @ecomp(^type = "configuration")
+ Parameter parameter
+ @ecomp(^type = "configuration")
+ String[] resource
+ @ecomp(^type = "configuration")
+ ComputationalOperator operator
+ @ecomp(^type = "configuration")
+ LocationInfo customerLocationInfo
+}
+
+enum PlacementOptimizationPolicyType{
+ placementOptimization=1
+}
+
+/*
+ * @param operator An operator in an expression.
+ * @param parameterAttributes Represents sub-expression
+ */
+class ObjectiveParameter{
+ @ecomp(^type = "configuration")
+ ComputationalOperator operator
+ @ecomp(^type = "configuration")
+ contains AttributeProperty[] parameterAttributes
+}
+
+/*
+ * Model of the placement optimization policy.
+ * @param type This is the type of a policy.
+ * @param objective This is an objective function.
+ * @param objectiveParameter The parameter/expression to be optimized.
+ */
+@policy (
+ policyTemplate = "SNIRO-PLACEMENT"
+)
+
+class PlacementOptimizationPolicy extends SniroPolicyMetaInfo{
+ @policy (matching = "true")
+ @ecomp(^type = "configuration")
+ PlacementOptimizationPolicyType ^type
+ @ecomp(^type = "configuration")
+ ObjectiveFunction objective
+ @ecomp(^type = "configuration")
+ contains ObjectiveParameter objectiveParameter
+}
+
+
+/*
+ * Meta information required for SNIRO policies.
+ * @param identity This is a user-defined identity.
+ * @param policyScope The scope of a policy
+ */
+
+@policy (
+ policyTemplate = "SNIRO"
+)
+class SniroPolicyMetaInfo{
+ @ecomp(^type = "configuration")
+ String identity
+ @ecomp(^type = "configuration")
+ @policy (matching = "true")
+ contains Scope policyScope
+}
+
+/*
+ * Scopes in which a policy is applicable.
+ * @param serviceType The type of a service.
+ * @param networkType The type of a network
+ * @param geoRigion The geographical region.
+ * @param resourceInstanceType The resources associated with a policy/
+ * @param subscriberRole
+ */
+class Scope{
+ @ecomp(^type = "configuration")
+ @policy (matching = "true")
+ String[] serviceType
+ @ecomp(^type = "configuration")
+ @policy (matching = "true")
+ String[] networkType
+ @ecomp(^type = "configuration")
+ @policy (matching = "true")
+ String[] geoRegion
+ @ecomp(^type = "configuration")
+ @policy (matching = "true")
+ String[] resourceInstanceType
+ @ecomp(^type = "configuration")
+ @policy (matching = "true")
+ String[] modelInvariantId
+ @ecomp(^type = "configuration")
+ @policy (matching = "true")
+ String[] subscriberRole
+} \ No newline at end of file
diff --git a/osdf/models/policy/placement/xacml/placementPolicies.xcore b/osdf/models/policy/placement/xacml/placementPolicies.xcore
new file mode 100644
index 0000000..866488e
--- /dev/null
+++ b/osdf/models/policy/placement/xacml/placementPolicies.xcore
@@ -0,0 +1,728 @@
+/*
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. 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.
+*/
+
+package org.onap.oof.osdf.policies.placement
+
+import java.util.UUID
+
+annotation "http://org.onap" as onap
+annotation "http://org.onap.policy" as policy
+
+type UUID wraps UUID
+
+/*
+ * Comparison operators
+ */
+enum ComparisonOperator{
+ less=1, grater=2, lessEqual=3, greaterEqual=4, equal=5, notEqual=6
+}
+
+enum TenantType{
+ GW_TENANT_ID=1, PORTAL_TENANT_ID=2
+}
+
+/*
+ * Computational operators
+ */
+enum ComputationalOperator{
+ sum=1, product=2
+}
+
+
+/*
+ * Qualifier types
+ */
+enum Qualifier{
+ same=1, different=2
+}
+
+/*
+ * Zone category
+ */
+enum ZoneCategory{
+ disaster=1, region=2, complex=3, time=4, maintenance=5
+}
+
+/*
+ * Geographical region types
+ */
+enum GeoRegion{
+ US=1, EMEA=2, AP=3, CALA=4, CA=5, INTERNATIONAL=6
+}
+
+/*
+ * Parameters
+ */
+enum Parameter{
+ distance=0, latency=1, cloud_version=2
+ //thoughput=1, geoDistance=1, airDistance=2, latency=3, bandwidth=4
+}
+
+
+/*
+ * The type of inventory defined in cloud
+ */
+enum InventoryType{
+ service=1, cloud=2
+}
+
+/*
+ * The type of network
+ */
+ enum NetworkType{
+ ip=1
+ }
+
+/*
+ * Objective functions.
+ */
+
+enum ObjectiveFunction{
+ minimize=1, maximize=2
+}
+
+/*
+ * This is a model of a condition.
+ * @param parameter This is the parameter of interest.
+ * @param operator This is a comparison operator.
+ * @param value This is a value of a parameter
+ */
+class ConditionalInfo{
+ @onap(^type = "configuration")
+ Parameter parameter
+ @onap(^type = "configuration")
+ ComparisonOperator operator
+ @onap(^type = "configuration")
+ String value
+}
+
+enum LocationInfo{
+ customer_loc=1, none=2, customer_pref_loc=3
+}
+
+
+/*
+ * Model for distance to location property.
+ * @param distanceCondition This is a distance condition.
+ * @param locationInfo This is a location with respect to which distance condition is applied.
+ */
+class DistanceToLocationProperty{
+ //distanceCondition.parameter must be distance.
+ @onap(^type = "configuration")
+ contains ConditionalInfo distanceCondition
+ @onap(^type = "configuration")
+ LocationInfo locationInfo
+}
+
+enum DistanceToLocationPolicyType{
+ distance_to_location=1
+}
+
+/*
+ * Model for distance to location policy.
+ * @param identity This is an identity created by a user.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resource instances over which this policy is applied.
+ * @param distanceToLocationProperty This is the distance properties of the policy.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class DistanceToLocationPolicy extends OOFPolicyMetaInfo{
+ @onap(^type = "configuration")
+ @policy (matching = "true")
+ DistanceToLocationPolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains DistanceToLocationProperty distanceToLocationProperty
+}
+
+/*
+ * The property associated with the NetworkBetweenDemandsPolicy.
+ * @param latencyCondition The latency property associated with the policy.
+ * @param locationInfo The customer location information.
+ */
+class NetworkToLocationProperty{
+ //latencyCondition.parameter must be latency.
+ @onap(^type = "configuration")
+ contains ConditionalInfo latencyCondition
+ @onap(^type = "configuration")
+ LocationInfo locationInfo
+}
+
+enum NetworkToLocationPolicyType{
+ network_to_location=1
+}
+
+/*
+ * Network between demands policy.
+ * @param type The type of a policy.
+ * @param resourceInstanceType The resources associated with a policy.
+ * @param networkToLocationProperty The property associated with the policy.
+ */
+
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class NetworkToLocationPolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ NetworkToLocationPolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains NetworkToLocationProperty networkToLocationProperty
+}
+
+/*
+ * The property associated with the NetworkBetweenDemandsPolicy.
+ * @param latencyCondition The latency property associated with the policy.
+ */
+class NetworkBetweenDemandsProperty{
+ //latencyCondition.parameter must be latency.
+ @onap(^type = "configuration")
+ contains ConditionalInfo latencyCondition
+}
+
+enum NetworkBetweenDemandsPolicyType{
+ network_between_demands=1
+}
+
+/*
+ * Network between demands policy.
+ * @param type The type of a policy.
+ * @param resourceInstanceType The resources associated with a policy.
+ * @param networkBetweenDemandsProperty The property associated with the policy.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class NetworkBetweenDemandsPolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ NetworkBetweenDemandsPolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains NetworkBetweenDemandsProperty networkBetweenDemandsProperty
+}
+
+
+/*
+ * Network roles supported a VNF placement
+ * @param all A list of network roles.
+ */
+class NetworkRoles{
+ @onap(^type = "configuration")
+ String[] all
+}
+
+/*
+ * Complex names supported by a VNF placement
+ * @param any A list of complex names.
+ */
+class Complex{
+ @onap(^type = "configuration")
+ String[] any
+}
+/*
+ * This are the cloud properties.
+ * @param cloudVersion This is the version of cloud.
+ * @param cloudType This is the type of cloud.
+ * @param dataPlace This is the type of data plane.
+ * @param hypervisor This is the type of hypervisor.
+ * @param networkRoles This is a list of connected networks.
+ * @param exclusivityGroups This is an exclusivity group Id
+ * @param state State in which a VNF should be located.
+ * @param country Country in which a VNF should be located.
+ * @param getRegion Geographical region in which a VNF should be located.
+ */
+class cloudAttributeProperty{
+ //cloudVersionCondition.parameter must be cloudVersion.
+ @onap(^type = "configuration")
+ String cloudVersion
+ @onap(^type = "configuration")
+ String cloudType
+ @onap(^type = "configuration")
+ String dataPlane
+ @onap(^type = "configuration")
+ String hypervisor
+ @onap(^type = "configuration")
+ contains NetworkRoles networkRoles
+ @onap(^type = "configuration")
+ contains Complex complex
+ @onap(^type = "configuration")
+ String exclusivityGroups
+ @onap(^type = "configuration")
+ String state
+ @onap(^type = "configuration")
+ String country
+ @onap(^type = "configuration")
+ GeoRegion geoRegion
+ @onap(^type = "configuration")
+ String replicationRole
+
+}
+
+enum cloudAttributePolicyType{
+ attribute=1
+}
+
+/*
+ * Model for the cloud attribute policy.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources over which the policy is applied.
+ * @param cloudAttributeProperty This is the properties associated with the policy.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class cloudAttributePolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ cloudAttributePolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains cloudAttributeProperty cloudAttributeProperty
+}
+
+/*
+ * The property associated with the capacity policy.
+ * @param tenant The tenant whose capacity needs to be checked.
+ * @param description The location of a heat template.
+ */
+class CapacityProperty{
+ @onap(^type = "configuration")
+ TenantType tenant
+ @onap(^type = "configuration")
+ String description
+}
+
+enum CapacityPolicyType{
+ cloud_capacity=1
+}
+
+/*
+ * Capacity policy
+ * @param type The type of a policy.
+ * @param resourceInstanceType The type of resources associated with a policy.
+ * @param capacityProperty The property associated with a policy.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class CapacityPolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ CapacityPolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains CapacityProperty capacityProperty
+}
+
+enum InventoryGroupPolicyType{
+ inventory_group = 1
+}
+
+/*
+ * Model for the inventory group policy.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources that must be grouped/paired
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class InventoryGroupPolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ InventoryGroupPolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+}
+
+/*
+ * This is the property associated with this policy.
+ * @param controller onap controller.
+ * @param request This should be key-value pairs to be sent in a request.
+ */
+class ResourceInstanceProperty{
+ @onap(^type = "configuration")
+ String controller
+ @onap(^type = "configuration")
+ String request
+}
+
+enum ResourceInstancePolicyType{
+ instance_fit=1
+}
+/*
+ * Model for the resource instance policy.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources.
+ * @param resourceInstanceProperty This is a property associated with each resource in the list.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class ResourceInstancePolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ ResourceInstancePolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains ResourceInstanceProperty resourceInstanceProperty
+
+}
+
+/*
+ * This is the property associated with this policy.
+ * @param controller onap controller
+ * @param request This should be key-value pairs to be sent in a request.
+ */
+class ResourceRegionProperty{
+ @onap(^type = "configuration")
+ String controller
+ @onap(^type = "configuration")
+ String request
+}
+
+enum ResourceRegionPolicyType{
+ region_fit=1
+}
+
+/*
+ * Model for the resource region policy
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources.
+ * @param resourceRegionProperty This is a property associated with this policy.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class ResourceRegionPolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ ResourceRegionPolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains ResourceRegionProperty resourceRegionProperty
+}
+
+/*
+ * This is the property associated with zone policy.
+ * @param qualifier This is the qualifier.
+ * @param category This is the category of a zone.
+ */
+class ZoneProperty{
+ @onap(^type = "configuration")
+ Qualifier qualifier
+ @onap(^type = "configuration")
+ ZoneCategory category
+}
+
+enum ZonePolicyType{
+ zone=1
+}
+
+/*
+ * Model of the zone policy.
+ * @param type This is the type of a policy.
+ * @param resourceInstanceType This is a list of resources.
+ * @param zoneProperty This is the property associated with the policy.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+
+class ZonePolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ ZonePolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains ZoneProperty zoneProperty
+}
+
+/*
+ * The property associated with a VNF type.
+ * @param inventoryProvider The onap entity providing inventory information.
+ * @param inventoryType The type of an inventory.
+ * @param serviceId The id of a service.
+ */
+class VNFPolicyProperty{
+ @onap(^type = "configuration")
+ String inventoryProvider
+ @onap(^type = "configuration")
+ InventoryType inventoryType
+ @onap(^type = "configuration")
+ contains Attributes attributes
+}
+
+/*
+ * The property associated with a Subscriber type.
+ * @param subscriberName The name of a subscriber.
+ * @param subscriberRole The role of a subscriber.
+ * @param provStatus The provisioning status of a subscriber.
+ */
+class SubscriberPolicyProperty{
+ @onap(^type = "configuration")
+ String[] subscriberName
+ @onap(^type = "configuration")
+ String[] subscriberRole
+ @onap(^type = "configuration")
+ String[] provStatus
+}
+
+enum VNFPolicyType{
+ vnfPolicy=1
+}
+
+enum SubscriberPolicyType{
+ subscriberPolicy=1
+}
+
+class Attributes{
+ @onap(^type = "configuration")
+ String globalCustomerId
+ @onap(^type = "configuration")
+ String operationalStatus
+ @onap(^type = "configuration")
+ String[] orchestrationStatus
+ @onap(^type = "configuration")
+ String modelInvariantId
+ @onap(^type = "configuration")
+ String modelVersionId
+ @onap(^type = "configuration")
+ String equipmentRole
+}
+
+/*
+ * Policy associated with a VNF.
+ * @param resourceInstance This parameter identifies a specific VNF.
+ * @param inventoryProvider This is the provider of inventory.
+ * @param inventoryType This is the type of inventory.
+ * @param serviceType The service associated with a VNF.
+ * @param serviceId The Id associated with a service.
+ * @param globalCustomerId The global id of a customer.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class VNFPolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ VNFPolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains VNFPolicyProperty[] property
+}
+
+/*
+ * Policy associated with a Subscriber.
+ * @param subscriberName The name of a subscriber.
+ * @param subscriberRole The role of a subscriber.
+ * @param provStatus The provisioning status of a subscriber.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class SubscriberPolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ SubscriberPolicyType ^type
+ @onap(^type = "configuration")
+ contains SubscriberPolicyProperty[] property
+}
+
+
+/*
+ * This is the property associated with this policy.
+ * @param providerUrl This is the url of provider to check the capacity.
+ * @param request This should be key-value pairs to be sent in a request.
+ */
+class InstanceReservationProperty{
+ @onap(^type = "configuration")
+ String controller
+ @onap(^type = "configuration")
+ String request
+}
+
+enum InstanceReservationPolicyType{
+ instance_reservation=1
+}
+/*
+ * Model for the resource instance policy.
+ * @param identity This is an identity created by a user.
+ * @param type This is the type of a policy.
+ * @param resourceInstance This is a list of resources.
+ * @param resourceInstanceProperty This is a property associated with each resource in the list.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class InstanceReservationPolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ InstanceReservationPolicyType ^type
+ @onap(^type = "configuration")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ contains InstanceReservationProperty instanceReservationProperty
+
+}
+
+/*
+ * This is a model of an operand.
+ * @param parameter This is a parameter.
+ * @param associativity This is a list of entities with which a parameter is associated.
+ */
+ /*
+class Operand{
+ @onap(^type = "configuration")
+ Parameter parameter
+ @onap(^type = "configuration")
+ Entity associativity
+}
+*/
+
+/*
+ * This is the optimization function.
+ * @param identity This is an identity of a function.
+ * @param operation This is a computational operator.
+ * @param leftOperand This is a left operand of a function.
+ * @param rightOperand This is a right operand of a function.
+ */
+/*
+class OptimizationFunction{
+ @onap(^type = "configuration")
+ ExpressionIdentity identity
+ @onap(^type = "configuration")
+ ComputationalOperator operation
+ @onap(^type = "configuration")
+ contains Operand[] operands
+}
+*/
+
+/*
+ * Properties associated with a sub-expression.
+ * @param weight The weight of an expression.
+ * @param parameter The parameter involved in an expression.
+ * @param entity The entities involved in an expression.
+ * @param operator The operator of an expression.
+ * @param customerLocationInfo The location of a customer.
+ */
+class AttributeProperty{
+ @onap(^type = "configuration")
+ double weight
+ @onap(^type = "configuration")
+ Parameter parameter
+ @onap(^type = "configuration")
+ String[] resource
+ @onap(^type = "configuration")
+ ComputationalOperator operator
+ @onap(^type = "configuration")
+ LocationInfo customerLocationInfo
+}
+
+enum PlacementOptimizationPolicyType{
+ placementOptimization=1
+}
+
+/*
+ * @param operator An operator in an expression.
+ * @param parameterAttributes Represents sub-expression
+ */
+class ObjectiveParameter{
+ @onap(^type = "configuration")
+ ComputationalOperator operator
+ @onap(^type = "configuration")
+ contains AttributeProperty[] parameterAttributes
+}
+
+/*
+ * Model of the placement optimization policy.
+ * @param type This is the type of a policy.
+ * @param objective This is an objective function.
+ * @param objectiveParameter The parameter/expression to be optimized.
+ */
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+
+class PlacementOptimizationPolicy extends OOFPolicyMetaInfo{
+ @policy (matching = "true")
+ @onap(^type = "configuration")
+ PlacementOptimizationPolicyType ^type
+ @onap(^type = "configuration")
+ ObjectiveFunction objective
+ @onap(^type = "configuration")
+ contains ObjectiveParameter objectiveParameter
+}
+
+
+/*
+ * Meta information required for oof policies.
+ * @param identity This is a user-defined identity.
+ * @param policyScope The scope of a policy
+ */
+
+@policy (
+ policyTemplate = "OOF-PLACEMENT"
+)
+class OOFPolicyMetaInfo{
+ @onap(^type = "configuration")
+ String identity
+ @onap(^type = "configuration")
+ @policy (matching = "true")
+ contains Scope policyScope
+}
+
+/*
+ * Scopes in which a policy is applicable.
+ * @param serviceType The type of a service.
+ * @param networkType The type of a network
+ * @param geoRigion The geographical region.
+ * @param resourceInstanceType The resources associated with a policy/
+ * @param subscriberRole
+ */
+class Scope{
+ @onap(^type = "configuration")
+ @policy (matching = "true")
+ String[] serviceType
+ @onap(^type = "configuration")
+ @policy (matching = "true")
+ String[] networkType
+ @onap(^type = "configuration")
+ @policy (matching = "true")
+ String[] geoRegion
+ @onap(^type = "configuration")
+ @policy (matching = "true")
+ String[] resourceInstanceType
+ @onap(^type = "configuration")
+ @policy (matching = "true")
+ String[] modelInvariantId
+ @onap(^type = "configuration")
+ @policy (matching = "true")
+ String[] subscriberRole
+}
diff --git a/osdf/operation/__init__.py b/osdf/operation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/operation/__init__.py
diff --git a/osdf/operation/error_handling.py b/osdf/operation/error_handling.py
new file mode 100644
index 0000000..dfb0848
--- /dev/null
+++ b/osdf/operation/error_handling.py
@@ -0,0 +1,93 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 schematics.exceptions import DataError
+
+from requests import RequestException
+from requests import ConnectionError, HTTPError, Timeout
+from osdf.operation.exceptions import BusinessException
+
+import osdf
+
+ERROR_TEMPLATE = osdf.ERROR_TEMPLATE
+
+MESSAGE_BASE = "A solution couldn't be determined because an external application"
+HTTP_ERROR_MESSAGE = MESSAGE_BASE + " returned a HTTP error"
+TIMEOUT_ERROR_MESSAGE = MESSAGE_BASE + " could not respond in time, please check the external application"
+CONNECTION_ERROR_MESSAGE = MESSAGE_BASE + " could not be reached"
+
+internal_error_body = {
+ "serviceException": {
+ "text": "Unhandled internal exception, request could not be processed"
+ }
+}
+
+internal_error_message = json.dumps(internal_error_body)
+
+
+def build_json_error_body(error):
+ if isinstance(error,RequestException):
+ return request_exception_to_json_body(error)
+ elif isinstance(error, DataError):
+ return data_error_to_json_body(error)
+ elif type(error) is BusinessException: # return the error message, because it is well formatted
+ return ERROR_TEMPLATE.render(description=str(error))
+ else:
+ return internal_error_message
+
+
+def data_error_to_json_body(error):
+ description = str(error).replace('"', '\\"')
+ error_message = ERROR_TEMPLATE.render(description=description)
+ return error_message
+
+
+def request_exception_to_json_body(error):
+ friendly_message = "A request exception has occurred when contacting an external system"
+ if type(error) is HTTPError:
+ friendly_message = HTTP_ERROR_MESSAGE
+ if type(error) is ConnectionError:
+ friendly_message = CONNECTION_ERROR_MESSAGE
+ if type(error) is Timeout:
+ friendly_message = TIMEOUT_ERROR_MESSAGE
+
+ eie_body = {
+ "serviceException": {
+ "text": friendly_message,
+ "errorType": "InterfaceError"
+ },
+ "externalApplicationDetails": {
+ "httpMethod": error.request.method,
+ "url": error.request.url
+ }
+ }
+
+ response = error.response
+
+ if response is not None:
+ eie_body['externalApplicationDetails']['httpStatusCode'] = response.status_code
+ content_type = response.headers.get('content-type')
+ if content_type is not None:
+ if 'application/json' in content_type:
+ eie_body['externalApplicationDetails']['responseMessage'] = response.json()
+ elif 'text/html' in content_type:
+ eie_body['externalApplicationDetails']['responseMessage'] = response.text
+ error_message = json.dumps(eie_body)
+ return error_message
diff --git a/osdf/operation/exceptions.py b/osdf/operation/exceptions.py
new file mode 100644
index 0000000..5277b01
--- /dev/null
+++ b/osdf/operation/exceptions.py
@@ -0,0 +1,40 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+
+class BusinessException(Exception):
+ pass
+
+
+class MessageBusConfigurationException(Exception):
+ pass
+
+
+class CMDataError(Exception):
+ pass
+
+
+class CMSOExecutionError(Exception):
+ pass
+
+
+class CMSOCallBackError(Exception):
+ pass
+
+
+class CMSOInvalidRequestException(Exception):
+ pass
diff --git a/osdf/operation/responses.py b/osdf/operation/responses.py
new file mode 100644
index 0000000..22a94f7
--- /dev/null
+++ b/osdf/operation/responses.py
@@ -0,0 +1,39 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 flask import Response
+
+from osdf import ACCEPTED_MESSAGE_TEMPLATE
+
+
+def osdf_response_for_request_accept(req_id="", text="", response_code=202, as_http=True):
+ """Helper method to create a response object for request acceptance, so that the object can be sent to a client
+ :param req_id: request ID provided by the caller
+ :param text: extra text description about accepting the request (e.g. "Request accepted")
+ :param response_code: the HTTP status code to send -- default is 202 (accepted)
+ :param as_http: whether to send response as HTTP response object or as a string
+ :return: if as_http is True, return a HTTP Response object. Otherwise, return json-encoded-message
+ """
+ response_message = ACCEPTED_MESSAGE_TEMPLATE.render(description=text, request_id=req_id)
+ if not as_http:
+ return response_message
+
+ response = Response(response_message, content_type='application/json; charset=utf-8')
+ response.headers.add('content-length', len(response_message))
+ response.status_code = response_code
+ return response
diff --git a/osdf/optimizers/__init__.py b/osdf/optimizers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/optimizers/__init__.py
diff --git a/osdf/optimizers/licenseopt/__init__.py b/osdf/optimizers/licenseopt/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/optimizers/licenseopt/__init__.py
diff --git a/osdf/optimizers/licenseopt/simple_license_allocation.py b/osdf/optimizers/licenseopt/simple_license_allocation.py
new file mode 100644
index 0000000..1b5b670
--- /dev/null
+++ b/osdf/optimizers/licenseopt/simple_license_allocation.py
@@ -0,0 +1,56 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 requests import RequestException
+from osdf.datasources.sdc import sdc, constraint_handler
+from osdf.logging.osdf_logging import audit_log, metrics_log, MH
+from osdf.config.base import osdf_config
+from osdf.utils import data_mapping
+
+
+def license_optim(request_json):
+ """
+ Fetch license artifacts associated with the service model and search licensekey-group-UUID and entitlement-pool-uuid
+ associated with the given att part number and nominal throughput in a request
+ :param request_json: Request in a JSON format
+ :return: A tuple of licensekey-group-uuid-list and entitlement-group-uuid-list
+ """
+ req_id = request_json["requestInfo"]["requestId"]
+ config = osdf_config.deployment
+
+ model_name = request_json['placementInfo']['serviceModelInfo']['modelName']
+ service_name = data_mapping.get_service_type(model_name)
+
+ license_info = []
+
+ order_info = json.loads(request_json["placementInfo"]["orderInfo"])
+ if service_name == 'VPE':
+ data_mapping.normalize_user_params(order_info)
+ for licenseDemand in request_json['placementInfo']['demandInfo']['licenseDemand']:
+ metrics_log.info(MH.requesting("sdc", req_id))
+ license_artifacts = sdc.request(licenseDemand['resourceModelInfo']['modelVersionId'],request_json["requestInfo"]["requestId"], config)
+ entitlement_pool_uuids, license_key_group_uuids = constraint_handler.choose_license(license_artifacts,order_info, service_name)
+ license_info.append(
+ {'serviceResourceId': licenseDemand['serviceResourceId'],
+ 'resourceModuleName': licenseDemand['resourceModuleName'],
+ 'entitlementPoolList': entitlement_pool_uuids,
+ 'licenseKeyGroupList': license_key_group_uuids
+ })
+ return license_info
diff --git a/osdf/optimizers/placementopt/__init__.py b/osdf/optimizers/placementopt/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/optimizers/placementopt/__init__.py
diff --git a/osdf/optimizers/placementopt/conductor/__init__.py b/osdf/optimizers/placementopt/conductor/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/optimizers/placementopt/conductor/__init__.py
diff --git a/osdf/optimizers/placementopt/conductor/api_builder.py b/osdf/optimizers/placementopt/conductor/api_builder.py
new file mode 100644
index 0000000..0a874f7
--- /dev/null
+++ b/osdf/optimizers/placementopt/conductor/api_builder.py
@@ -0,0 +1,123 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 copy
+import json
+# from osdf.utils import data_mapping
+from jinja2 import Template
+from osdf.utils.programming_utils import list_flatten, dot_notation
+import osdf.optimizers.placementopt.conductor.translation as tr
+from osdf.adapters.policy.utils import group_policies
+
+
+def conductor_api_builder(request_json, flat_policies: list, local_config, prov_status,
+ template="templates/conductor_interface.json"):
+ """Build a SNIRO southbound API call for Conductor/Placement optimization
+ :param request_json: parameter data received from a client
+ :param flat_policies: policy data received from the policy platform (flat policies)
+ :param template: template to generate southbound API call to conductor
+ :param local_config: local configuration file with pointers for the service specific information
+ :param prov_status: provStatus retrieved from Subscriber policy
+ :return: json to be sent to Conductor/placement optimization
+ """
+ templ = Template(open(template).read())
+ gp = group_policies(flat_policies)
+ demand_vnf_name_list = []
+
+ for placementDemand in request_json['placementInfo']['demandInfo']['placementDemand']:
+ demand_vnf_name_list.append(placementDemand['resourceModuleName'])
+
+ demand_list = tr.gen_demands(request_json['placementInfo']['demandInfo'], gp['vnfPolicy'])
+ attribute_policy_list = tr.gen_attribute_policy(demand_vnf_name_list, gp['attribute'])
+ distance_to_location_policy_list = tr.gen_distance_to_location_policy(
+ demand_vnf_name_list, gp['distance_to_location'])
+ inventory_policy_list = tr.gen_inventory_group_policy(demand_vnf_name_list, gp['inventory_group'])
+ resource_instance_policy_list = tr.gen_resource_instance_policy(
+ demand_vnf_name_list, gp['instance_fit'])
+ resource_region_policy_list = tr.gen_resource_region_policy(demand_vnf_name_list, gp['region_fit'])
+ zone_policy_list = tr.gen_zone_policy(demand_vnf_name_list, gp['zone'])
+ optimization_policy_list = tr.gen_optimization_policy(demand_vnf_name_list, gp['placementOptimization'])
+ reservation_policy_list = tr.gen_reservation_policy(demand_vnf_name_list, gp['instance_reservation'])
+ conductor_policies = [attribute_policy_list, distance_to_location_policy_list, inventory_policy_list,
+ resource_instance_policy_list, resource_region_policy_list, zone_policy_list]
+ filtered_policies = [x for x in conductor_policies if len(x) > 0]
+ policy_groups = list_flatten(filtered_policies)
+ reservation_policies = [x for x in reservation_policy_list if len(x) > 0]
+ reservation_groups = list_flatten(reservation_policies)
+ req_info = request_json['requestInfo']
+ model_name = request_json['placementInfo']['serviceModelInfo']['modelName']
+ service_type = model_name
+ # service_type = data_mapping.get_service_type(model_name)
+ service_info = local_config.get('service_info', {}).get(service_type, {})
+ order_info = {}
+ if 'orderInfo' in request_json["placementInfo"]:
+ order_info = json.loads(request_json["placementInfo"]["orderInfo"])
+ request_type = req_info.get('requestType', None)
+ subs_com_site_id = ""
+ if 'subscriberInfo' in request_json['placementInfo']:
+ subs_com_site_id = request_json['placementInfo']['subscriberInfo'].get('subscriberCommonSiteId', "")
+ if service_type == 'vCPE':
+ # data_mapping.normalize_user_params(order_info)
+ rendered_req = templ.render(
+ requestType=request_type,
+ chosenComplex=subs_com_site_id,
+ demand_list=demand_list,
+ policy_groups=policy_groups,
+ optimization_policies=optimization_policy_list,
+ name=req_info['requestId'],
+ timeout=req_info['timeout'],
+ limit=req_info['numSolutions'],
+ serviceType=service_type,
+ serviceInstance=request_json['placementInfo']['serviceInstanceId'],
+ provStatus = prov_status,
+ chosenRegion=order_info.get('requestParameters',{}).get('lcpCloudRegionId'),
+ json=json)
+ elif service_type == 'UNKNOWN':
+ rendered_req = templ.render(
+ requestType=request_type,
+ chosenComplex=subs_com_site_id,
+ demand_list=demand_list,
+ policy_groups=policy_groups,
+ reservation_groups=reservation_groups,
+ optimization_policies=optimization_policy_list,
+ name=req_info['requestId'],
+ timeout=req_info['timeout'],
+ limit=req_info['numSolutions'],
+ serviceType=service_type,
+ serviceInstance=request_json['placementInfo']['serviceInstanceId'],
+ provStatus = prov_status,
+ # process order data
+ bandwidth=dot_notation(order_info, service_info['bandwidth']),
+ bandwidth_unit=dot_notation(order_info, service_info['bandwidth_units']),
+ json=json)
+ json_payload = json.dumps(json.loads(rendered_req)) # need this because template's JSON is ugly!
+ return json_payload
+
+
+def retrieve_node(req_json, reference):
+ """
+ Get the child node(s) from the dot-notation [reference] and parent [req_json].
+ For placement and other requests, there are encoded JSONs inside the request or policy,
+ so we need to expand it and then do a search over the parent plus expanded JSON.
+ """
+ req_json_copy = copy.deepcopy(req_json) # since we expand the JSON in place, we work on a copy
+ if 'orderInfo' in req_json_copy['placementInfo']:
+ req_json_copy['placementInfo']['orderInfo'] = json.loads(req_json_copy['placementInfo']['orderInfo'])
+ info = dot_notation(req_json_copy, reference)
+ return list_flatten(info) if isinstance(info, list) else info
+
diff --git a/osdf/optimizers/placementopt/conductor/conductor.py b/osdf/optimizers/placementopt/conductor/conductor.py
new file mode 100644
index 0000000..bdc7f17
--- /dev/null
+++ b/osdf/optimizers/placementopt/conductor/conductor.py
@@ -0,0 +1,186 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+
+"""
+This application generates conductor API calls using the information received from SO and Policy platform.
+"""
+
+import json
+import time
+
+from jinja2 import Template
+from requests import RequestException
+
+from osdf.logging.osdf_logging import debug_log
+from osdf.optimizers.placementopt.conductor.api_builder import conductor_api_builder
+from osdf.utils.interfaces import RestClient
+from osdf.operation.exceptions import BusinessException
+
+
+def request(req_object, osdf_config, grouped_policies, prov_status):
+ """
+ Process a placement request from a Client (build Conductor API call, make the call, return result)
+ :param req_object: Request parameters from the client
+ :param osdf_config: Configuration specific to SNIRO application (core + deployment)
+ :param grouped_policies: policies related to placement (fetched based on request, and grouped by policy type)
+ :param prov_status: provStatus retrieved from Subscriber policy
+ :return: response from Conductor (accounting for redirects from Conductor service
+ """
+ config = osdf_config.deployment
+ local_config = osdf_config.core
+ uid, passwd = config['conductorUsername'], config['conductorPassword']
+ conductor_url = config['conductorUrl']
+ req_id = req_object['requestInfo']['requestId']
+ transaction_id = req_object['requestInfo']['transactionId']
+ headers = dict(transaction_id=transaction_id)
+
+ max_retries = config.get('conductorMaxRetries', 30)
+ ping_wait_time = config.get('conductorPingWaitTime', 60)
+
+ rc = RestClient(userid=uid, passwd=passwd, method="GET", log_func=debug_log.debug, headers=headers)
+ conductor_req_json_str = conductor_api_builder(req_object, grouped_policies, local_config, prov_status)
+ conductor_req_json = json.loads(conductor_req_json_str)
+
+ debug_log.debug("Sending first Conductor request for request_id {}".format(req_id))
+ resp, raw_resp = initial_request_to_conductor(rc, conductor_url, conductor_req_json)
+ # Very crude way of keeping track of time.
+ # We are not counting initial request time, first call back, or time for HTTP request
+ total_time, ctr = 0, 2
+ client_timeout = req_object['requestInfo']['timeout']
+ configured_timeout = max_retries * ping_wait_time
+ max_timeout = min(client_timeout, configured_timeout)
+
+ while True: # keep requesting conductor till we get a result or we run out of time
+ if resp is not None:
+ if resp["plans"][0].get("status") in ["error"]:
+ raise RequestException(response=raw_resp, request=raw_resp.request)
+
+ if resp["plans"][0].get("status") in ["done", "not found"]:
+ if resp["plans"][0].get("recommendations"):
+ return conductor_response_processor(resp, raw_resp, req_id)
+ else: # "solved" but no solutions found
+ return conductor_no_solution_processor(resp, raw_resp, req_id)
+ new_url = resp['plans'][0]['links'][0][0]['href'] # TODO: check why a list of lists
+
+ if total_time >= max_timeout:
+ raise BusinessException("Conductor could not provide a solution within {} seconds, this transaction is timing out".format(max_timeout))
+ time.sleep(ping_wait_time)
+ ctr += 1
+ debug_log.debug("Attempt number {} url {}; prior status={}".format(ctr, new_url, resp['plans'][0]['status']))
+ total_time += ping_wait_time
+
+ try:
+ raw_resp = rc.request(new_url, raw_response=True)
+ resp = raw_resp.json()
+ except RequestException as e:
+ debug_log.debug("Conductor attempt {} for request_id {} has failed because {}".format(ctr, req_id, str(e)))
+
+
+def initial_request_to_conductor(rc, conductor_url, conductor_req_json):
+ """First steps in the request-redirect chain in making a call to Conductor
+ :param rc: REST client object for calling conductor
+ :param conductor_url: conductor's base URL to submit a placement request
+ :param conductor_req_json: request json object to send to Conductor
+ :return: URL to check for follow up (similar to redirects); we keep checking these till we get a result/error
+ """
+ debug_log.debug("Payload to Conductor: {}".format(json.dumps(conductor_req_json)))
+ raw_resp = rc.request(url=conductor_url, raw_response=True, method="POST", json=conductor_req_json)
+ resp = raw_resp.json()
+ if resp["status"] != "template":
+ raise RequestException(response=raw_resp, request=raw_resp.request)
+ time.sleep(10) # 10 seconds wait time to avoid being too quick!
+ plan_url = resp["links"][0][0]["href"]
+ debug_log.debug("Attemping to read the plan from the conductor provided url {}".format(plan_url))
+ raw_resp = rc.request(raw_response=True, url=plan_url) # TODO: check why a list of lists for links
+ resp = raw_resp.json()
+
+ if resp["plans"][0]["status"] in ["error"]:
+ raise RequestException(response=raw_resp, request=raw_resp.request)
+ return resp, raw_resp # now the caller of this will handle further follow-ups
+
+
+def conductor_response_processor(conductor_response, raw_response, req_id):
+ """Build a response object to be sent to client's callback URL from Conductor's response
+ This includes Conductor's placement optimization response, and required ASDC license artifacts
+
+ :param conductor_response: JSON response from Conductor
+ :param raw_response: Raw HTTP response corresponding to above
+ :param req_id: Id of a request
+ :return: JSON object that can be sent to the client's callback URL
+ """
+ composite_solutions = []
+ name_map = {"physical-location-id": "cloudClli", "host_id": "vnfHostName",
+ "cloud_version": "cloudVersion", "cloud_owner": "cloudOwner"}
+ for reco in conductor_response['plans'][0]['recommendations']:
+ for resource in reco.keys():
+ c = reco[resource]['candidate']
+ solution = {
+ 'resourceModuleName': resource,
+ 'serviceResourceId': reco[resource]['service_resource_id'],
+ 'inventoryType': c['inventory_type'],
+ 'serviceInstanceId': c['candidate_id'] if c['inventory_type'] == "service" else "",
+ 'cloudRegionId': c['location_id'],
+ 'assignmentInfo': []
+ }
+
+ for key, value in reco[resource]['attributes'].items():
+ try:
+ solution['assignmentInfo'].append({"variableName": name_map[key], "variableValue": value})
+ except KeyError:
+ debug_log.debug("The key[{}] is not mapped and will not be returned in assignment info".format(key))
+
+ if c.get('host_id'):
+ solution['assignmentInfo'].append({'variableName': name_map['host_id'], 'variableValue': c['host_id']})
+ composite_solutions.append(solution)
+
+ request_state = conductor_response['plans'][0]['status']
+ transaction_id = raw_response.headers.get('transaction_id', "")
+ status_message = conductor_response.get('plans')[0].get('message', "")
+
+ solution_info = {}
+ if composite_solutions:
+ solution_info['placementInfo'] = composite_solutions
+
+ resp = {
+ "transactionId": transaction_id,
+ "requestId": req_id,
+ "requestState": request_state,
+ "statusMessage": status_message,
+ "solutionInfo": solution_info
+ }
+ return resp
+
+
+def conductor_no_solution_processor(conductor_response, raw_response, request_id,
+ template_placement_response="templates/plc_opt_response.jsont"):
+ """Build a response object to be sent to client's callback URL from Conductor's response
+ This is for case where no solution is found
+
+ :param conductor_response: JSON response from Conductor
+ :param raw_response: Raw HTTP response corresponding to above
+ :param request_id: request Id associated with the client request (same as conductor response's "name")
+ :param template_placement_response: the template for generating response to client (plc_opt_response.jsont)
+ :return: JSON object that can be sent to the client's callback URL
+ """
+ status_message = conductor_response["plans"][0].get("message")
+ templ = Template(open(template_placement_response).read())
+ return json.loads(templ.render(composite_solutions=[], requestId=request_id,
+ transactionId=raw_response.headers.get('transaction_id', ""),
+ statusMessage=status_message, json=json))
+
+
diff --git a/osdf/optimizers/placementopt/conductor/remote_opt_processor.py b/osdf/optimizers/placementopt/conductor/remote_opt_processor.py
new file mode 100644
index 0000000..f753a70
--- /dev/null
+++ b/osdf/optimizers/placementopt/conductor/remote_opt_processor.py
@@ -0,0 +1,79 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 requests import RequestException
+
+import traceback
+from osdf.operation.error_handling import build_json_error_body
+from osdf.logging.osdf_logging import metrics_log, MH, error_log
+from osdf.optimizers.placementopt.conductor import conductor
+from osdf.optimizers.licenseopt.simple_license_allocation import license_optim
+from osdf.utils.interfaces import get_rest_client
+
+
+def process_placement_opt(request_json, policies, osdf_config, prov_status):
+ """Perform the work for placement optimization (e.g. call SDC artifact and make conductor request)
+ NOTE: there is scope to make the requests to policy asynchronous to speed up overall performance
+ :param request_json: json content from original request
+ :param policies: flattened policies corresponding to this request
+ :param osdf_config: configuration specific to OSDF app
+ :param prov_status: provStatus retrieved from Subscriber policy
+ :return: None, but make a POST to callback URL
+ """
+
+ try:
+ rc = get_rest_client(request_json, service="so")
+ req_id = request_json["requestInfo"]["requestId"]
+ transaction_id = request_json['requestInfo']['transactionId']
+
+ metrics_log.info(MH.inside_worker_thread(req_id))
+ license_info = None
+ if 'licenseDemand' in request_json['placementInfo']['demandInfo']:
+ license_info = license_optim(request_json)
+
+ # Conductor only handles placement, only call Conductor if placementDemands exist
+ if 'placementDemand' in request_json['placementInfo']['demandInfo']:
+ metrics_log.info(MH.requesting("placement/conductor", req_id))
+ placement_response = conductor.request(request_json, osdf_config, policies, prov_status)
+ if license_info: # Attach license solution if it exists
+ placement_response['solutionInfo']['licenseInfo'] = license_info
+ else: # License selection only scenario
+ placement_response = {
+ "transactionId": transaction_id,
+ "requestId": req_id,
+ "requestState": "complete",
+ "statusMessage": "License selection completed successfully",
+ "solutionInfo": {"licenseInfo": license_info}
+ }
+ except Exception as err:
+ error_log.error("Error for {} {}".format(req_id, traceback.format_exc()))
+
+ try:
+ body = build_json_error_body(err)
+ metrics_log.info(MH.sending_response(req_id, "ERROR"))
+ rc.request(json=body, noresponse=True)
+ except RequestException:
+ error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc()))
+ return
+
+ try:
+ metrics_log.info(MH.calling_back_with_body(req_id, rc.url,placement_response))
+ rc.request(json=placement_response, noresponse=True)
+ except RequestException : # can't do much here but log it and move on
+ error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc()))
+
diff --git a/osdf/optimizers/placementopt/conductor/translation.py b/osdf/optimizers/placementopt/conductor/translation.py
new file mode 100644
index 0000000..262fa86
--- /dev/null
+++ b/osdf/optimizers/placementopt/conductor/translation.py
@@ -0,0 +1,216 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 osdf.utils.data_conversion import text_to_symbol
+# from osdf.utils import data_mapping
+
+def gen_optimization_policy(vnf_list, optimization_policy):
+ """Generate optimization policy details to pass to Conductor
+ :param vnf_list: List of vnf's to used in placement request
+ :param optimization_policy: optimization policy information provided in the incoming request
+ :return: List of optimization policies in a format required by Conductor
+ """
+ optimization_policy_list = []
+ for policy in optimization_policy:
+ content = policy['content']
+ parameter_list = []
+
+ for attr in content['objectiveParameter']['parameterAttributes']:
+ parameter = attr['parameter'] if attr['parameter'] == "cloud_version" else attr['parameter']+"_between"
+ for res in attr['resource']:
+ vnf = get_matching_vnf(res, vnf_list)
+ value = [vnf] if attr['parameter'] == "cloud_version" else [attr['customerLocationInfo'], vnf]
+ parameter_list.append({
+ attr['operator']: [attr['weight'], {parameter: value}]
+ })
+
+ optimization_policy_list.append({
+ content['objective']: {content['objectiveParameter']['operator']: parameter_list }
+ })
+ return optimization_policy_list
+
+
+def get_matching_vnf(resource, vnf_list):
+
+ for vnf in vnf_list:
+ if resource in vnf:
+ return vnf
+ return resource
+
+
+def get_matching_vnfs(resources, vnf_list, match_type="intersection"):
+ """Get a list of matching VNFs from the list of resources
+ :param resources:
+ :param vnf_list: List of vnf's to used in placement request
+ :param match_type: "intersection" or "all" or "any" (any => send all_vnfs if there is any intersection)
+ :return: List of matching VNFs
+ """
+ common_vnfs = []
+ for vnf in vnf_list:
+ for resource in resources:
+ if resource in vnf:
+ common_vnfs.append(vnf)
+ if match_type == "intersection": # specifically requested intersection
+ return common_vnfs
+ elif common_vnfs or match_type == "all": # ("any" and common) OR "all"
+ return resources
+ return None
+
+
+def gen_policy_instance(vnf_list, resource_policy, match_type="intersection", rtype=None):
+ """Generate a list of policies
+ :param vnf_list: List of vnf's to used in placement request
+ :param resource_policy: policy for this specific resource
+ :param match_type: How to match the vnf_names with the vnf_list (intersection or "any")
+ intersection => return intersection; "any" implies return all vnf_names if intersection is not null
+ :param rtype: resource type (e.g. resourceRegionProperty or resourceInstanceProperty)
+ None => no controller information added to the policy specification to Conductor
+ :return: resource policy list in a format required by Conductor
+ """
+ resource_policy_list = []
+ related_policies = []
+ for policy in resource_policy:
+ pc = policy['content']
+ demands = get_matching_vnfs(pc['resourceInstanceType'], vnf_list, match_type=match_type)
+ resource = {pc['identity']: {'type': pc['type'], 'demands': demands}}
+
+ if rtype:
+ resource[pc['identity']]['properties'] = {'controller': pc[rtype]['controller'],
+ 'request': json.loads(pc[rtype]['request'])}
+ if demands and len(demands) != 0:
+ resource_policy_list.append(resource)
+ related_policies.append(policy)
+ return resource_policy_list, related_policies
+
+
+def gen_resource_instance_policy(vnf_list, resource_instance_policy):
+ """Get policies governing resource instances in order to populate the Conductor API call"""
+ cur_policies, _ = gen_policy_instance(vnf_list, resource_instance_policy, rtype='resourceInstanceProperty')
+ return cur_policies
+
+
+def gen_resource_region_policy(vnf_list, resource_region_policy):
+ """Get policies governing resource region in order to populate the Conductor API call"""
+ cur_policies, _ = gen_policy_instance(vnf_list, resource_region_policy, rtype='resourceRegionProperty')
+ return cur_policies
+
+
+def gen_inventory_group_policy(vnf_list, inventory_group_policy):
+ """Get policies governing inventory group in order to populate the Conductor API call"""
+ cur_policies, _ = gen_policy_instance(vnf_list, inventory_group_policy, rtype=None)
+ return cur_policies
+
+
+def gen_reservation_policy(vnf_list, reservation_policy):
+ """Get policies governing resource instances in order to populate the Conductor API call"""
+ cur_policies, _ = gen_policy_instance(vnf_list, reservation_policy, rtype='instanceReservationProperty')
+ return cur_policies
+
+
+def gen_distance_to_location_policy(vnf_list, distance_to_location_policy):
+ """Get policies governing distance-to-location for VNFs in order to populate the Conductor API call"""
+ cur_policies, related_policies = gen_policy_instance(vnf_list, distance_to_location_policy, rtype=None)
+ for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy
+ properties = p_main['content']['distanceToLocationProperty']
+ pcp_d = properties['distanceCondition']
+ p_new[p_main['content']['identity']]['properties'] = {
+ 'distance': text_to_symbol[pcp_d['operator']] + " " + pcp_d['value'].lower(),
+ 'location': properties['locationInfo']
+ }
+ return cur_policies
+
+
+def gen_attribute_policy(vnf_list, attribute_policy):
+ """Get policies governing attributes of VNFs in order to populate the Conductor API call"""
+ cur_policies, related_policies = gen_policy_instance(vnf_list, attribute_policy, rtype=None)
+ for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy
+ properties = p_main['content']['cloudAttributeProperty']
+ p_new[p_main['content']['identity']]['properties'] = {
+ 'evaluate': {
+ 'hypervisor': properties.get('hypervisor', ''),
+ 'cloud_version': properties.get('cloudVersion', ''),
+ 'cloud_type': properties.get('cloudType', ''),
+ 'dataplane': properties.get('dataPlane', ''),
+ 'network_roles': properties.get('networkRoles', ''),
+ 'complex': properties.get('complex', ''),
+ 'state': properties.get('state', ''),
+ 'country': properties.get('country', ''),
+ 'geo_region': properties.get('geoRegion', ''),
+ 'exclusivity_groups': properties.get('exclusivityGroups', ''),
+ 'replication_role': properties.get('replicationRole', '')
+ }
+ }
+ return cur_policies
+
+
+def gen_zone_policy(vnf_list, zone_policy):
+ """Get zone policies in order to populate the Conductor API call"""
+ cur_policies, related_policies = gen_policy_instance(vnf_list, zone_policy, rtype=None)
+ for p_new, p_main in zip(cur_policies, related_policies): # add additional fields to each policy
+ pmz = p_main['content']['zoneProperty']
+ p_new[p_main['content']['identity']]['properties'] = {'category': pmz['category'], 'qualifier': pmz['qualifier']}
+ return cur_policies
+
+
+def get_demand_properties(demand, policies):
+ """Get list demand properties objects (named tuples) from policy"""
+ def _get_candidates(candidate_info):
+ return [dict(inventory_type=x['candidateType'], candidate_id=x['candidates']) for x in candidate_info]
+ properties = []
+ for policy in policies:
+ for resourceInstanceType in policy['content']['resourceInstanceType']:
+ if resourceInstanceType in demand['resourceModuleName']:
+ for x in policy['content']['property']:
+ property = dict(inventory_provider=x['inventoryProvider'],
+ inventory_type=x['inventoryType'],
+ service_resource_id=demand['serviceResourceId'])
+ if 'attributes' in x:
+ attributes = {}
+ for k,v in x['attributes'].items():
+ # key=data_mapping.convert(k)
+ key = k
+ attributes[key] = v
+ if(key=="model-invariant-id"):
+ attributes[key]=demand['resourceModelInfo']['modelInvariantId']
+ elif(key=="model-version-id"):
+ attributes[key]=demand['resourceModelInfo']['modelVersionId']
+ property.update({"attributes": attributes})
+ if x['inventoryType'] == "cloud":
+ property['region'] = {'get_param': "CHOSEN_REGION"}
+ if 'exclusionCandidateInfo' in demand:
+ property['excluded_candidates'] = _get_candidates(demand['exclusionCandidateInfo'])
+ if 'requiredCandidateInfo' in demand:
+ property['required_candidates'] = _get_candidates(demand['requiredCandidateInfo'])
+ properties.append(property)
+ if len(properties) == 0:
+ properties.append(dict(customer_id="", service_type="", inventory_provider="", inventory_type=""))
+ return properties
+
+
+def gen_demands(req_json, vnf_policies):
+ """Generate list of demands based on request and VNF policies
+ :param req_json: Request object from the client (e.g. MSO)
+ :param vnf_policies: Policies associated with demand resources (e.g. from grouped_policies['vnfPolicy'])
+ :return: list of demand parameters to populate the Conductor API call
+ """
+ demand_dictionary = {}
+ for placementDemand in req_json['placementDemand']:
+ demand_dictionary.update({placementDemand['resourceModuleName']: get_demand_properties(placementDemand, vnf_policies)})
+
+ return demand_dictionary
diff --git a/osdf/templates/cms_opt_request.jsont b/osdf/templates/cms_opt_request.jsont
new file mode 100755
index 0000000..006562b
--- /dev/null
+++ b/osdf/templates/cms_opt_request.jsont
@@ -0,0 +1,35 @@
+{
+ "transaction_id": "{{ transaction_id }}",
+ "request_id": "{{ request_id }}",
+ "start_date" : "{{ start_time }}",
+ "end_date" : "{{ end_time }}",
+ "change_elements" : {{ json.dumps(change_elements) }},
+ "constraints" : [
+ {
+ "type" : "general_concurrency_limit",
+ "parameters": [{{ concurrency_limit }}]
+ },
+
+ {
+ "type" : "allowed_forbidden_periods",
+ "parameters" : {{ json.dumps(allowed_periods) }}
+ }
+
+ {% if spatial_conflicts is defined and spatial_conflicts|length > 0 %}
+ ,
+ {
+ "type" : "spatial_conflict",
+ "parameters": {{ json.dumps(spatial_conflicts) }}
+ }
+ {% endif %}
+
+
+ {% if critical_periods is defined and spatial_conflicts|length > 0 %}
+ ,
+ {
+ "type" : "critical_periods",
+ "parameters": {{ json.dumps(critical_periods) }}
+ }
+ {% endif %}
+ ]
+}
diff --git a/osdf/templates/cms_opt_request.jsont_1707_v1 b/osdf/templates/cms_opt_request.jsont_1707_v1
new file mode 100755
index 0000000..75ecbe5
--- /dev/null
+++ b/osdf/templates/cms_opt_request.jsont_1707_v1
@@ -0,0 +1,67 @@
+{
+ "transaction_id": "{{ transaction_id }}",
+ "request_id": "{{ request_id }}",
+ "start_date" : "{{ start_time }}",
+ "end_date" : "{{ end_time }}",
+
+ "change_elements" : [
+ {% set comma = joiner(",") -%}
+ {% for element in all_upgrades -%} {{ comma() }}
+ {
+ "id" : "{{ element.id }}",
+ "failback_duration": {{ element.failback_duration }},
+ {% if element.group_id -%}
+ "group_id": "{{ element.group_id }}",
+ {% endif %}
+ {% if element.scheduled_on -%}
+ "scheduled_on": "{{ element.scheduled_on }}",
+ {% endif %}
+ "duration": {{ element.duration }}
+ }
+ {% endfor -%}
+ ],
+
+ "constraints" : [
+ {
+ "type" : "general_concurrency_limit",
+ "parameters": [{{ concurrency_limit }}]
+ },
+
+ {
+ "type" : "allowed_forbidden_periods",
+ "parameters" : [
+ {% set comma = joiner(",") -%}
+ {% for idx in all_pending -%} {{ comma() }}
+ { "id" : "{{ idx.id }}",
+ "allowed_periods": [ {{ allowed_periods }}]
+ }
+ {% endfor -%}
+ ]
+ },
+ {
+ "type" : "spatial_conflict",
+ "parameters": [
+ {% set comma = joiner(",") -%}
+ {% for pserver, vce_list in vce_pserver_mapping.items() -%} {{ comma() }}
+ {
+ "spatial_entity": "{{ pserver }}",
+ "affected_entities": {{ vce_list }}
+ }
+ {% endfor -%}
+ ]
+ },
+
+ {
+ "type" : "critical_periods",
+ "parameters": [
+ {% set comma = joiner(",") -%}
+ {% for element, conflict_period in conflict_interval.items() -%} {{ comma() }}
+ {
+ "id" : "{{ element }}",
+ "periods": [{{ conflict_period }}]
+ }
+ {% endfor -%}
+ ]
+ }
+ ]
+}
diff --git a/osdf/templates/cms_opt_request_1702.jsont b/osdf/templates/cms_opt_request_1702.jsont
new file mode 100755
index 0000000..bcafa45
--- /dev/null
+++ b/osdf/templates/cms_opt_request_1702.jsont
@@ -0,0 +1,63 @@
+{
+ "request_id": "{{ request_id }}",
+ "startdate" : "{{ start_time }}",
+ "enddate" : "{{ end_time }}",
+
+ "change_elements" : [
+{% set comma = joiner(",") -%}
+{% for element in all_upgrades -%} {{ comma() }}
+ { "id" : "{{ element.id }}",
+ {% if element.scheduled -%} "scheduled_on": "{{ element.scheduled }}", {% endif -%}
+ "duration": {{ element.duration }}, {# duration in seconds #}
+ "failback_duration": {{ element.failback_duration }}, {# duration in seconds #}
+ "group_id": {{ element.group_id }}, {# duration in seconds #}
+ }{% endfor -%}
+ ],
+
+ "constraints" : [
+ {
+ "type" : "general_concurrency_limit",
+ "parameters" : [ {{ general_concurrency_limit }} ]
+ },
+
+ {
+ "type" : "allowed_forbidden_periods",
+ "parameters" : [
+{% set comma = joiner(",") -%}
+{% for idx in all_pending -%} {{ comma() }}
+ { "id" : "{{ idx.id }}",
+ "allowed_periods": [ {% set comma2 = joiner(",") -%}
+ {% for period in allowed_periods -%} {{ comma2() }} [{{ json.dumps(period[0]) }}, {{ json.dumps(period[1]) }}]
+ {% endfor -%} ] }{% endfor -%}
+ ]
+ }
+
+{% if p_v_conflict is defined and p_v_conflict|length > 0 %}
+ ,
+ {
+ "type" : "critical_periods",
+ "description" : "Simultaneous upgrades",
+ "parameters" : [
+{% set comma2 = joiner(",") -%}
+{% for element in p_v_conflict -%} {{ comma2() }}
+ {
+ "id" : "{{ element[0] }}",
+ "periods" : [{{ json.dumps(element[0]) }}, {{ json.dumps(element[1]) }}]
+ }
+{% endfor -%}
+{% endif %}
+
+{% for pserver, vce_group in grouped_vces.items() -%} {{ comma() }}
+ ,
+ {
+ "id" : "{{ pserver }}",
+ "name" : "VCE's on pserver {{ pserver }}",
+ "description": "Only some VCEs on a pserver can be upgraded at a time",
+ "max_num_upgrades" : {{ max_num_upgrades(vce_group) }},
+ "upgrades" : {{ json.dumps(vce_group) }}
+ }
+{% endfor -%}
+ ]
+ }
+ ]
+}
diff --git a/osdf/templates/cms_opt_response.jsont b/osdf/templates/cms_opt_response.jsont
new file mode 100644
index 0000000..a8817df
--- /dev/null
+++ b/osdf/templates/cms_opt_response.jsont
@@ -0,0 +1,8 @@
+{
+ "transactionId": "{{transaction_id}}",
+ "scheduleId":"{{schedule_id}}",
+ "requestState": "{{request_state}}",
+ "status": "{{status}}",
+ "description": "{{description}}",
+ "schedule": {{schedule}}
+} \ No newline at end of file
diff --git a/osdf/templates/conductor_interface.json b/osdf/templates/conductor_interface.json
new file mode 100755
index 0000000..2b48647
--- /dev/null
+++ b/osdf/templates/conductor_interface.json
@@ -0,0 +1,81 @@
+{
+ "name": "{{ name }}",
+ "files": {},
+ "timeout": {{ timeout }},
+ "limit": {{ limit }},
+ "template": {
+ "conductor_template_version": "2018-02-01",
+ "parameters": {
+ "REQUEST_TYPE": "{{ requestType }}",
+ "CHOSEN_REGION": "{{ chosenRegion }}",
+ "LATITUDE": "{{ latitude }}",
+ "LONGITUDE": "{{ longitude }}",
+ {% if serviceType == 'DHV' %}
+ "E2EVPNKEY": "{{ e2eVpnKey }}",
+ "UCPEHOST": "{{ ucpeHostName }}",
+ "EFFECTIVE_BANDWIDTH": "{{ effectiveBandwidth }}",
+ "WAN_PORT1_UP": "{{ ipsec_bw_up }}",
+ "WAN_PORT1_DOWN": "{{ ipsec_bw_down }}",
+ "WAN_PORT2_UP": "{{ ipsec2_bw_up }}",
+ "WAN_PORT2_DOWN": "{{ ipsec2_bw_down }}",
+ {% endif %}
+ {% if serviceType != 'DHV' %}
+ "GW_TENANT_ID": "{{ gwTenantId }}",
+ "PORTAL_TENANT_ID": "{{ portalTenantId }}",
+ {% endif %}
+ "CHOSEN_COMPLEX": "{{ chosenComplex }}",
+ {% if serviceType == 'ADIOD' or serviceType == 'VPE' %}
+ "BANDWIDTH": "{{ bandwidth }}",
+ "UNIT": "{{ bandwidth_unit }}",
+ {% endif %}
+ "SERVICE_INST": "{{ serviceInstance }}",
+ "PROV_STATUS": {{ json.dumps(provStatus) }}
+ },
+ "locations": {
+ {% if serviceType == 'DHV' %}
+ "customer_loc": {
+ "host_name": { "get_param": "UCPEHOST" }
+ }
+ {% elif serviceType == 'ADIOD' %}
+ "customer_pref_location": {
+ "clli_code": { "get_param": "CHOSEN_COMPLEX" }
+ }
+ {% elif serviceType == 'NETBOND' %}
+ "peering_point": {
+ "latitude": { "get_param": "LATITUDE" },
+ "longitude": { "get_param": "LONGITUDE" }
+ }
+ {% else %}
+ "customer_pref_loc": {
+ "clli_code": { "get_param": "CHOSEN_COMPLEX" }
+ }
+ {% endif %}
+ },
+ "demands": {{ json.dumps(demand_list) }},
+ {% set comma_main = joiner(",") %}
+ "constraints": {
+ {% set comma=joiner(",") %}
+ {% for elem in policy_groups %} {{ comma() }}
+ {% for key, value in elem.items() %}
+ "{{key}}": {{ json.dumps(value) }}
+ {% endfor %}
+ {% endfor %}
+ },
+ "reservation": {
+ {% set comma=joiner(",") %}
+ {% for elem in reservation_groups %} {{ comma() }}
+ {% for key, value in elem.items() %}
+ "{{key}}": {{ json.dumps(value) }}
+ {% endfor %}
+ {% endfor %}
+ },
+ "optimization": {
+ {% set comma=joiner(",") %}
+ {% for elem in optimization_policies %} {{ comma() }}
+ {% for key, value in elem.items() %}
+ "{{key}}": {{ json.dumps(value) }}
+ {% endfor %}
+ {% endfor %}
+ }
+ }
+} \ No newline at end of file
diff --git a/osdf/templates/license_opt_request.jsont b/osdf/templates/license_opt_request.jsont
new file mode 100644
index 0000000..7baa759
--- /dev/null
+++ b/osdf/templates/license_opt_request.jsont
@@ -0,0 +1,6 @@
+{
+ "transactionId": "{{transaction_id}}",
+ "requestId": "{{request_id}}",
+ "partNumber": "{{part_number}}",
+ "licenseModel" : "{{artifact}}"
+} \ No newline at end of file
diff --git a/osdf/templates/plc_opt_request.jsont b/osdf/templates/plc_opt_request.jsont
new file mode 100755
index 0000000..cd78b3e
--- /dev/null
+++ b/osdf/templates/plc_opt_request.jsont
@@ -0,0 +1,142 @@
+{
+ "name": "{{ name }}",
+ "files": "{{ files }}",
+ "timeout": "{{ timeout }}",
+ "limit": "{{ limit }}",
+ "template": {
+ "CUST_ID": "{{ cust_id }}",
+ "E2EVPNKEY": "{{ e2evpnkey }}",
+ "UCPEHOST": "{{ ucpehost }}",
+ "WAN_PORT1_UP": "{{ wan_port1_up }}",
+ "WAN_PORT1_DOWN": "{{ wan_port1_down }}",
+ "EFFECTIVE_BANDWIDTH": "{{ effective_bandwidth }}",
+ "SERVICE_INST": "{{ service_inst }}",
+ "locations": {
+ "customer_loc": {
+ "host_name": "{{ ucpehost }}"
+ }
+ },
+ "demands": [
+ {% set comma=joiner(",") %}
+ {% for demand in demand_list %} {{ comma() }}
+ {
+ "{{ demand.vnf_name }}": [
+ {% set comma2=joiner(",") %}
+ {% for property in demand.property %}
+ "inventory_provider": {{ property.inventory_provider }},
+ "inventory_type": {{ property.inventory_type }},
+ "service_type": {{ property.service_type }},
+ "customer_id": {{ property.customer_id }},
+ "candidate_id": {{ property.candidate_id }}
+ {% endfor %}
+ ]
+ }
+ {% endfor %}
+ ],
+ "constraints": {
+ {% set comma_main=joiner(",") %}
+
+ {% if attribute_policy_list %} {{ comma_main() }} {% endif %}
+ {% set comma=joiner(",") %}
+ {% for attribute in attribute_policy_list %} {{ comma() }}
+ attribute['identity'] : {
+ "type": {{ attribute['type'] }},
+ "demands": {{ attribute['demands'] }},
+ "properties": {
+ "evaluate": {
+ "hypervisor": {{ attribute['property']['hypervisor'] }},
+ "aic_version": {{ attribute['property']['aicVersion'] }},
+ "aic_type": {{ attribute['property']['aicType'] }},
+ "dataplane": {{ attribute['property']['datatype'] }},
+ "network_roles": {{ attribute['property']['networkRoles'] }},
+ "complex": {{ attribute['property']['complex'] }}
+ }
+ }
+ }
+ {% endfor %}
+
+ {% if distance_to_location_policy_list %} {{ comma_main() }} {% endif %}
+ {% set comma=joiner(",") %}
+ {% for distance_location in distance_to_location_policy_list %} {{ comma() }}
+ distance_location['identity'] : {
+ "type": {{ distance_location['type'] }},
+ "demands": {{ distance_location['demands'] }},
+ "properties": {
+ "distance": {{ distance_location['property']['distance'] }},
+ "location": {{ distance_location['property']['location'] }}
+ }
+ }
+ {% endfor %}
+
+ {% if inventory_policy_list %} {{ comma_main() }} {% endif %}
+ {% set comma=joiner(",") %}
+ {% for inventory in inventory_policy_list %} {{ comma() }}
+ inventory['identity'] : {
+ "type": {{ inventory['type'] }},
+ "demands": {{ inventory['demands'] }}
+ }
+ {% endfor %}
+
+ {% if resource_instance_policy_list %} {{ comma_main() }} {% endif %}
+ {% set comma=joiner(",") %}
+ {% for resource_instance in resource_instance_policy_list %} {{ comma() }}
+ resource_instance['identity'] : {
+ "type": {{ resource_instance['type'] }},
+ "demands": {{ resource_instance['demands'] }},
+ "properties": {
+ "controller": {{ resource_instance['property']['controller'] }},
+ "request": {{ resource_instance['property']['request'] }}
+ }
+ }
+ {% endfor %}
+
+ {% if resource_region_policy_list %} {{ comma_main() }} {% endif %}
+ {% set comma=joiner(",") %}
+ {% for resource_region in resource_region_policy_list %} {{ comma() }}
+ resource_region['identity'] : {
+ "type": {{ resource_region['type'] }},
+ "demands": {{ resource_region['demands'] }},
+ "properties": {
+ "controller": {{ resource_region['property']['controller'] }},
+ "request": {{ resource_region['property']['request'] }}
+ }
+ }
+ {% endfor %}
+
+ {% if zone_policy_list %} {{ comma_main() }} {% endif %}
+ {% set comma=joiner(",") %}
+ {% for zone in zone_policy_list %} {{ comma() }}
+ zone['identity'] : {
+ "type": {{ zone['type'] }},
+ "demands": {{ zone['demands'] }},
+ "properties": {
+ "qualifier": {{ resource_region['property']['qualifier'] }},
+ "category": {{ resource_region['property']['category'] }}
+ }
+ }
+ {% endfor %}
+
+ {% if optmization_policy_list %} {{ comma_main() }} {% endif %}
+ {% set comma=joiner(",") %}
+ {% for optimization in optimization_policy_list %} {{ comma() }}
+ "optimization" : {
+ {{ optimization['objective'] }}: {
+ "sum": [
+ {% set comma2=joiner(",") %}
+ {% for parameter in optimization['parameter'] %} {{ comma() }}
+ {
+ "product": [
+ {{ parameter['weight'] }},
+ {
+ "distance_between": [{{ parameter['customerLocation'] }},{{ parameter['demand'] }}]
+ }
+ ]
+ }
+ {% endfor %}
+ ]
+ }
+ }
+ {% endfor %}
+ }
+ }
+} \ No newline at end of file
diff --git a/osdf/templates/plc_opt_response.jsont b/osdf/templates/plc_opt_response.jsont
new file mode 100755
index 0000000..aa678b5
--- /dev/null
+++ b/osdf/templates/plc_opt_response.jsont
@@ -0,0 +1,14 @@
+{
+ "requestId": "{{requestId}}",
+ "transactionId": "{{transacationId}}",
+ "startTime": "{{startTime}}",
+ "responseTime": "{{responseTime}}",
+ "requestType": "{{requestType}}",
+ "requestState": "{{requestState}}",
+ "statusMessage": "{{statusMessage}}",
+ "percentProgress": "{{percentProgress}}",
+ "solutionInfo": {
+ "placement": {{ json.dumps(composite_solutions) }},
+ "licenseInfo":{ "featureGroupId": "{{featureGroupId}}" }
+ }
+}
diff --git a/osdf/templates/policy_request.jsont b/osdf/templates/policy_request.jsont
new file mode 100755
index 0000000..3a9e201
--- /dev/null
+++ b/osdf/templates/policy_request.jsont
@@ -0,0 +1,3 @@
+{
+ "policyName": "{{policy_name}}" {# we currently only support query by policy name only -- policyName #}
+}
diff --git a/osdf/templates/test_cms_nb_req_from_client.jsont b/osdf/templates/test_cms_nb_req_from_client.jsont
new file mode 100755
index 0000000..a60c8ff
--- /dev/null
+++ b/osdf/templates/test_cms_nb_req_from_client.jsont
@@ -0,0 +1,19 @@
+{
+ "schedulingInfo": {
+ "change_management_id": "{{ change_management_id }}",
+ "start_time": "{{ start_time }}",
+ "end_time": "{{ end_time }}",
+ "policy_id": {{ json.dumps(policy_id) }}, {# a list of policy Ids #}
+ "service_type": "{{ service_type }}",
+ "workflow_type": "{{ workflow_type }}",
+ "upgrades": {{ json.dumps(upgrades) }} {# a list of node Ids #}
+ },
+ "requestInfo": {
+ "requestId": "{{ requestId }}",
+ "sourceId": "{{ sourceId }}",
+ "optimizer": "{{ optimizer }}",
+ "numSolutions": "{{ numSolutions }}",
+ "callbackUrl" : "{{ callbackUrl }}"
+ }
+}
+
diff --git a/osdf/templates/test_plc_nb_req_from_client.jsont b/osdf/templates/test_plc_nb_req_from_client.jsont
new file mode 100755
index 0000000..998ffb3
--- /dev/null
+++ b/osdf/templates/test_plc_nb_req_from_client.jsont
@@ -0,0 +1,52 @@
+{
+ "requestInfo": {
+ "requestId": "{{requestId}}",
+ "sourceId": "{{sourceId}}",
+ "optimizer": "{{optimizer}}",
+ "numSolutions": {{numSolutions}},
+ "timeout": {{timeout}},
+ "callbackUrl" : "{{callbackUrl}}"
+ },
+ "placementInfo": {
+ "modelInfo": {
+ "modelType": "{{modelType}}",
+ "modelInvariant": "{{modelInvariantId}}",
+ "modelVersionId": "{{modelVersionId}}",
+ "modelName": "{{modelName}}",
+ "modelVersion": "{{modelVersion}}",
+ "modelCustomizationId": "{{modelCustomizationId}}"
+ },
+ "subscriberInfo": {
+ "globalSubscriberId": "{{globalSubscriberId}}",
+ "subscriberName": "{{subscriberName}}",
+ "subscriberCommonSiteId": "{{subscriberCommonSiteId}}",
+ "ucpeHostName": "{{ucpeHostName}}"
+ },
+ "policyId": {{json.dumps(policyId)}},
+ "vnfInfo": {
+ "vnfType": "{{vnfType}}",
+ "vnfPartNumber": "{{vnfPartNumber}}",
+ "nominalThroughput": "{{nominalThroughput}}",
+ "vnfSoftwareVersion": "{{vnfSoftwareVersion}}",
+ "vnfManagementOption": "{{vnfManagementOption}}"
+ },
+ "vpnInfo": {
+ "vpnId": "{{vpnId}}",
+ "pvcId": "{{pvcId}}"
+ },
+ "serviceInfo": {
+ "dhvServiceInfo": {
+ "serviceInstanceId": "{{serviceInstanceId}}",
+ "serviceType": "{{serviceType}}",
+ "e2evpnkey": "{{e2evpnkey}}",
+ "dhvSiteEffectiveTransportBandwidth": {{dhvSiteEffectiveTransportBandwidth}},
+ "dhvIPSecTransportBandwidthUp": {{dhvIPSecTransportBandwidthUp}},
+ "dhvIPSecTransportBandwidthDown": {{dhvIPSecTransportBandwidthDown}},
+ "dhvIPSec2TransportBandwidthUp": {{dhvIPSec2TransportBandwidthUp}},
+ "dhvIPSec2TransportBandwidthDown": {{dhvIPSec2TransportBandwidthDown}},
+ "dhvVendorName": "{{dhvVendorName}}"
+ }
+ },
+ "demandInfo": {{json.dumps(demandInfo)}}
+ }
+}
diff --git a/osdf/utils/__init__.py b/osdf/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/utils/__init__.py
diff --git a/osdf/utils/data_conversion.py b/osdf/utils/data_conversion.py
new file mode 100644
index 0000000..2f678fa
--- /dev/null
+++ b/osdf/utils/data_conversion.py
@@ -0,0 +1,62 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 itertools
+from collections import defaultdict
+
+from dateutil import tz
+from dateutil.parser import parse
+
+
+def tuples_to_multi_val_dict(kvw_tuples, colnums=(0, 1)):
+ """Given a list of k,v tuples, get a dictionary of the form k -> [v1,v2,...,vn]
+ :param kvw_tuples: list of k,v,w tuples (e.g. [(k1,v1,a1), (k2,v2,a2), (k1,v3,a3), (k1,v4,a4)]
+ :param colnums: column numbers
+ :return: a dict of str:set, something like {k1: {v1, v3, v4}, k2: {v2}} or {k1: {a1, a3, a4}, k2: {a2}}
+ """
+ res = defaultdict(set)
+ for x in kvw_tuples:
+ key, val = x[colnums[0]], x[colnums[1]]
+ res[key].add(val)
+ return dict((k, set(v)) for k, v in res.items())
+
+
+def tuples_to_dict(kvw_tuples, colnums=(0, 1)):
+ """Given a list of k,v tuples, get a dictionary of the form k -> v
+ :param kvw_tuples: list of k,v,w tuples (e.g. [(k1,v1,a1), (k2,v2,a2), (k3,v3,a3), (k1,v4,a4)]
+ :param colnums: column numbers
+ :return: a dict; something like {k1: v4, k2: v2, k3: v3} (note, k1 is repeated, so last val is retained)
+ """
+ return dict((x[colnums[0]], x[colnums[1]]) for x in kvw_tuples)
+
+
+def utc_time_from_ts(timestamp):
+ """Return corresponding UTC timestamp for a given ISO timestamp (or anything that parse accepts)"""
+ return parse(timestamp).astimezone(tz.tzutc()).strftime('%Y-%m-%d %H:%M:%S')
+
+
+def list_flatten(l):
+ """Flatten a complex nested list of nested lists into a flat list"""
+ return itertools.chain(*[list_flatten(j) if isinstance(j, list) else [j] for j in l])
+
+
+text_to_symbol = {
+ 'greater': ">",
+ 'less': "<",
+ 'equal': "="
+}
diff --git a/osdf/utils/data_types.py b/osdf/utils/data_types.py
new file mode 100644
index 0000000..877d4a1
--- /dev/null
+++ b/osdf/utils/data_types.py
@@ -0,0 +1,30 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 collections
+
+
+def list_like(obj):
+ """Check if an object is a list-like object, but not a string or dict"""
+ return isinstance(obj, collections.Sequence) and not isinstance(obj, (str, bytes))
+
+
+def dict_like(obj):
+ """Check if an object is a list-like object, but not a string or dict"""
+ return isinstance(obj, collections.Mapping)
+
diff --git a/osdf/utils/interfaces.py b/osdf/utils/interfaces.py
new file mode 100644
index 0000000..7a0e3a9
--- /dev/null
+++ b/osdf/utils/interfaces.py
@@ -0,0 +1,90 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 requests
+
+from osdf.config.base import osdf_config, creds_prefixes
+from osdf.logging.osdf_logging import MH, debug_log
+
+
+def get_rest_client(request_json, service):
+ """Get a RestClient based on request_json's callback URL and osdf_config's credentials based on service name
+ :param request_json:
+ :param service: so or cm
+ :return: rc -- RestClient
+ """
+ callback_url = request_json["requestInfo"]["callbackUrl"]
+ prefix = creds_prefixes[service]
+ config = osdf_config.deployment
+ c_userid, c_passwd = config[prefix + "Username"], config[prefix + "Password"]
+ return RestClient(url=callback_url, userid=c_userid, passwd=c_passwd)
+
+
+class RestClient(object):
+ """Simple REST Client that supports get/post and basic auth"""
+
+ def __init__(self, userid=None, passwd=None, log_func=None, url=None, timeout=None, headers=None,
+ method="POST", req_id=None):
+ self.auth = (userid, passwd) if userid and passwd else None
+ self.headers = headers if headers else {}
+ self.method = method
+ self.url = url
+ self.log_func = log_func
+ self.timeout = (30, 90) if timeout is None else timeout
+ self.req_id = req_id
+
+ def add_headers(self, headers):
+ self.headers.update(headers)
+
+ def request(self, url=None, method=None, asjson=True, ok_codes=(2, ),
+ raw_response=False, noresponse=False, timeout=None, **kwargs):
+ """
+ :param url: REST end point to query
+ :param method: GET or POST (default is None => self.method)
+ :param asjson: whether the expected response is in json format
+ :param ok_codes: expected codes (prefix matching -- e.g. can be (20, 21, 32) or (2, 3))
+ :param noresponse: If no response is expected (as long as response codes are OK)
+ :param raw_response: If we need just the raw response (e.g. conductor sends transaction IDs in headers)
+ :param timeout: Connection and read timeouts
+ :param kwargs: Other parameters
+ :return:
+ """
+ if not self.req_id:
+ debug_log.debug("Requesting URL: {}".format(url or self.url))
+ else:
+ debug_log.debug("Requesting URL: {} for request ID: {}".format(url or self.url, self.req_id))
+
+ res = requests.request(url=url or self.url, method=method or self.method,
+ auth=self.auth, headers=self.headers,
+ timeout=timeout or self.timeout, **kwargs)
+
+ if self.log_func:
+ self.log_func(MH.received_http_response(res))
+
+ res_code = str(res.status_code)
+ if not any(res_code.startswith(x) for x in map(str, ok_codes)):
+ raise res.raise_for_status()
+
+ if raw_response:
+ return res
+ elif noresponse:
+ return None
+ elif asjson:
+ return res.json()
+ else:
+ return res.content
diff --git a/osdf/utils/local_processing.py b/osdf/utils/local_processing.py
new file mode 100644
index 0000000..6768839
--- /dev/null
+++ b/osdf/utils/local_processing.py
@@ -0,0 +1,43 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 os
+
+from osdf.logging.osdf_logging import metrics_log, MH, warn_audit_error
+
+
+def local_create_job_file(req_id, json_req, fname='osdf-req-data.json'):
+ """Creates a "work" folder for local processing and place relevant
+ job task file in there"""
+
+ work_dir = 'osdf-optim/work/' + req_id
+ work_file = '{}/{}'.format(work_dir, fname)
+ try:
+ cur_task = "Making a local directory in the OSDF manager for req-id: {}".format(req_id)
+ metrics_log.info(MH.creating_local_env(cur_task))
+ os.makedirs(work_dir, exist_ok=True)
+ except Exception as err:
+ warn_audit_error(MH.error_local_env(req_id, "Can't create directory {}".format(work_dir), err))
+ return None
+ try:
+ with open(work_file, 'w') as fid:
+ fid.write(json_req['payload'])
+ return work_dir
+ except Exception as err:
+ warn_audit_error(MH.error_local_env(req_id, "can't create file {}".format(work_file), err))
+ return None
diff --git a/osdf/utils/programming_utils.py b/osdf/utils/programming_utils.py
new file mode 100644
index 0000000..a0a8fde
--- /dev/null
+++ b/osdf/utils/programming_utils.py
@@ -0,0 +1,105 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 collections
+import itertools
+
+
+class DotDict(dict):
+ """A dot-dict mixin to be able to access a dictionary via dot notation
+ source: https://stackoverflow.com/questions/2352181/how-to-use-a-dot-to-access-members-of-dictionary
+ """
+ __getattr__ = dict.get
+ __setattr__ = dict.__setitem__
+ __delattr__ = dict.__delitem__
+
+
+class MetaSingleton(type):
+ """Singleton class (2nd Chapter) from Learning Python Design Patterns - 2nd ed.
+ Chetan Giridhar, Packt Publ. 2016"""
+ _instances = {}
+
+ def __call__(cls, *args, **kwargs):
+ if cls not in cls._instances:
+ cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
+ return cls._instances[cls]
+
+
+def namedtuple_with_defaults(typename, field_names, default_values=()):
+ """A namedtuple with default values -- Stack overflow recipe from Mark Lodato
+ http://stackoverflow.com/questions/11351032/named-tuple-and-optional-keyword-arguments
+ :param typename: Name for the class (same as for namedtuple)
+ :param field_names: Field names (same as for namedtuple)
+ :param default_values: Can be specified as a dictionary or as a list
+ :return: New namedtuple object
+ """
+ T = collections.namedtuple(typename, field_names)
+ T.__new__.__defaults__ = (None,) * len(T._fields)
+ if isinstance(default_values, collections.Mapping):
+ prototype = T(**default_values)
+ else:
+ prototype = T(*default_values)
+ T.__new__.__defaults__ = tuple(prototype)
+ return T
+
+
+def dot_notation(dict_like, dot_spec):
+ """Return the value corresponding to the dot_spec from a dict_like object
+ :param dict_like: dictionary, JSON, etc.
+ :param dot_spec: a dot notation (e.g. a1.b1.c1.d1 => a1["b1"]["c1"]["d1"])
+ :return: the value referenced by the dot_spec
+ """
+ attrs = dot_spec.split(".") # we split the path
+ parent = dict_like.get(attrs[0])
+ children = ".".join(attrs[1:])
+ if not (parent and children): # if no children or no parent, bail out
+ return parent
+ if isinstance(parent, list): # here, we apply remaining path spec to all children
+ return [dot_notation(j, children) for j in parent]
+ elif isinstance(parent, dict):
+ return dot_notation(parent, children)
+ else:
+ return None
+
+
+def list_flatten(l):
+ """
+ Flatten a complex nested list of nested lists into a flat list (DFS).
+ For example, [ [1, 2], [[[2,3,4], [2,3,4]], [3,4,5, 'hello']]]
+ will produce [1, 2, 2, 3, 4, 2, 3, 4, 3, 4, 5, 'hello']
+ """
+ return list(itertools.chain(*[list_flatten(j) if isinstance(j, list) else [j] for j in l]))
+
+
+def inverted_dict(keys: list, key_val_dict: dict) -> dict:
+ """
+ Get val -> [keys] mapping for the given keys using key_val_dict
+ :param keys: the keys we are interested in (a list)
+ :param key_val_dict: the key -> val mapping
+ :return: inverted dictionary of val -> [keys] (for the subset dict of given keys)
+ """
+ res = {}
+ all_tuples = ((k, key_val_dict[k] if k in key_val_dict else 'no-parent-' + k) for k in keys)
+ for k, v in all_tuples:
+ if v in res:
+ res[v].append(k)
+ else:
+ res[v] = [k]
+ # making sure to remove duplicate keys
+ res = dict((v, list(set(k_list))) for v, k_list in res.items())
+ return res
diff --git a/osdf/webapp/__init__.py b/osdf/webapp/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/osdf/webapp/__init__.py
diff --git a/osdf/webapp/appcontroller.py b/osdf/webapp/appcontroller.py
new file mode 100644
index 0000000..49f84ff
--- /dev/null
+++ b/osdf/webapp/appcontroller.py
@@ -0,0 +1,47 @@
+# -------------------------------------------------------------------------
+# Copyright (c) 2015-2017 AT&T Intellectual Property
+#
+# 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 flask import request
+from flask_httpauth import HTTPBasicAuth
+from flask import Response
+import json
+import osdf
+from osdf.config.base import http_basic_auth_credentials
+
+auth_basic = HTTPBasicAuth()
+
+error_body = {
+ "serviceException": {
+ "text": "Unauthorized, check username and password"
+ }
+}
+
+unauthorized_message = json.dumps(error_body)
+
+@auth_basic.get_password
+def get_pw(username):
+ end_point = request.url.split('/')[-1]
+ auth_group = osdf.end_point_auth_mapping.get(end_point)
+ return http_basic_auth_credentials[auth_group].get(username) if auth_group else None
+
+@auth_basic.error_handler
+def auth_error():
+ response = Response(unauthorized_message, content_type='application/json; charset=utf-8')
+ response.headers.add('content-length', len(unauthorized_message))
+ response.status_code = 401
+ return response