diff options
-rw-r--r-- | conductor/conductor/setup.cfg | 71 | ||||
-rw-r--r-- | conductor/conductor/setup.py | 34 | ||||
-rw-r--r-- | conductor/conductor/tests/unit/data/__init__.py | 0 | ||||
-rw-r--r-- | conductor/conductor/tests/unit/data/candidate_list.json | 43 | ||||
-rw-r--r-- | conductor/conductor/tests/unit/data/constraints.json | 95 | ||||
-rw-r--r-- | conductor/conductor/tests/unit/data/demands.json | 30 | ||||
-rw-r--r-- | conductor/conductor/tests/unit/data/test_service.py | 154 | ||||
-rw-r--r-- | conductor/pom.xml | 2 | ||||
-rw-r--r-- | conductor/setup.cfg | 2 | ||||
-rw-r--r-- | docs/api.rst | 133 | ||||
-rw-r--r-- | docs/index.rst | 1 | ||||
-rw-r--r-- | pom.xml | 15 |
12 files changed, 577 insertions, 3 deletions
diff --git a/conductor/conductor/setup.cfg b/conductor/conductor/setup.cfg new file mode 100644 index 0000000..8e3fc56 --- /dev/null +++ b/conductor/conductor/setup.cfg @@ -0,0 +1,71 @@ +[metadata] +name = of-has +summary = ONAP Homing Service +description-file = README.rst +author = AT&T +author-email = ikram@research.att.com +home-page = https://wiki.onap.org/pages/viewpage.action?pageId=16005528 +classifier = + Development Status :: 4 - Beta + Environment :: ONAP + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 +keywords = + onap + homing + conductor + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[files] +packages = + conductor +data_files = + etc/conductor = etc/conductor/* +# conductor_integrationtests +#scripts = +# bin/conductor-db-setup + +[entry_points] +wsgi_scripts = + conductor-api = conductor.api.app:build_wsgi_app + +console_scripts = + conductor-controller = conductor.cmd.controller:main + conductor-data = conductor.cmd.data:main + conductor-solver = conductor.cmd.solver:main + conductor-reservation = conductor.cmd.reservation:main + +conductor.inventory_provider.plugin = + aai = conductor.data.plugins.inventory_provider.aai:AAI + +conductor.service_controller.plugin = + sdnc = conductor.data.plugins.service_controller.sdnc:SDNC + +oslo.config.opts = + conductor = conductor.opts:list_opts + +oslo.config.opts.defaults = + conductor = conductor.conf.defaults:set_cors_middleware_defaults + +#tempest.test_plugins = +# conductor_tests = conductor_integrationtests.plugin:ConductorTempestPlugin + +#[build_sphinx] +#all_files = 1 +#build-dir = doc/build +#source-dir = doc/source + +[pbr] +warnerrors = true +autodoc_index_modules = true + diff --git a/conductor/conductor/setup.py b/conductor/conductor/setup.py new file mode 100644 index 0000000..0c696ed --- /dev/null +++ b/conductor/conductor/setup.py @@ -0,0 +1,34 @@ +# -*- encoding: utf-8 -*- +# ------------------------------------------------------------------------- +# 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. +# +# ------------------------------------------------------------------------- +# + +'''Setup''' + +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa # pylint: disable=W0611,C0411 +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr>=1.8'], + pbr=True) diff --git a/conductor/conductor/tests/unit/data/__init__.py b/conductor/conductor/tests/unit/data/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/conductor/conductor/tests/unit/data/__init__.py diff --git a/conductor/conductor/tests/unit/data/candidate_list.json b/conductor/conductor/tests/unit/data/candidate_list.json new file mode 100644 index 0000000..e29782c --- /dev/null +++ b/conductor/conductor/tests/unit/data/candidate_list.json @@ -0,0 +1,43 @@ +{ + "candidate_list": [ + { + "candidate_id": "1ac71fb8-ad43-4e16-9459-c3f372b8236d", + "candidate_type": "service", + "inventory_type": "service", + "inventory_provider": "aai", + "host_id": "vnf_123456", + "cost": "100", + "location_id": "DLLSTX55", + "location_type": "azure", + "latitude": "32.897480", + "longitude": "-97.040443", + "city": "Dallas", + "state": "TX", + "country": "USA", + "region": "US", + "complex_name": "dalls_one", + "cloud_owner": "att-aic", + "cloud_region_version": "1.1", + "physical_location_id": "DLLSTX55" + }, + { + "candidate_id": "NYCNY55", + "candidate_type": "cloud", + "inventory_type": "cloud", + "inventory_provider": "aai", + "cost": "100", + "location_id": "NYCNY55", + "location_type": "azure", + "latitude": "40.7128", + "longitude": "-74.0060", + "city": "New York", + "state": "NY", + "country": "USA", + "region": "US", + "complex_name": "ny_one", + "cloud_owner": "att-aic", + "cloud_region_version": "1.1", + "physical_location_id": "NYCNY55" + } + ] +}
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/constraints.json b/conductor/conductor/tests/unit/data/constraints.json new file mode 100644 index 0000000..f89cdaf --- /dev/null +++ b/conductor/conductor/tests/unit/data/constraints.json @@ -0,0 +1,95 @@ +{ + "constraint_name": "check_for_availability", + "candidate_list": [ + { + "candidate_id": "DLLSTX55", + "candidate_type": "cloud", + "inventory_type": "cloud", + "inventory_provider": "aai", + "cost": "100", + "location_id": "DLLSTX55", + "location_type": "azure", + "latitude": "32.897480", + "longitude": "-97.040443", + "city": "Dallas", + "state": "TX", + "country": "USA", + "region": "US", + "complex_name": "dalls_one", + "cloud_owner": "att-aic", + "cloud_region_version": "1.1", + "physical_location_id": "DLLSTX55" + }, + { + "candidate_id": "NYCNY55", + "candidate_type": "cloud", + "inventory_type": "cloud", + "inventory_provider": "aai", + "cost": "100", + "location_id": "NYCNY55", + "location_type": "azure", + "latitude": "40.7128", + "longitude": "-74.0060", + "city": "New York", + "state": "NY", + "country": "USA", + "region": "US", + "complex_name": "ny_one", + "cloud_owner": "att-aic", + "cloud_region_version": "1.1", + "physical_location_id": "NYCNY55" + }, + { + "candidate_id": "c3", + "candidate_type": "service", + "inventory_type": "service", + "inventory_provider": "aai", + "host_id": "vnf_333", + "cost": "100", + "location_id": "SFOCA55", + "location_type": "azure", + "latitude": "32.897480", + "longitude": "-97.040443", + "city": "San Francisco", + "state": "CA", + "country": "USA", + "region": "US", + "complex_name": "sfo_one", + "cloud_owner": "att-aic", + "cloud_region_version": "1.1", + "physical_location_id": "SFOCA55" + } + ], + "constraint_type": "instance_fit", + "controller": "SDN-C", + "request": { + "key1": "value1", + "key2": "value2", + "key3": "value3" + }, + "properties": { + "evaluate": { + "network_roles": "", + "complex_name": { + "any": [ + "dalls_one" + ] + }, + "country": { + "any": [ + "USA" + ] + }, + "state": { + "any": [ + "TX" + ] + }, + "region": { + "all": [ + "US" + ] + } + } + } +}
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/demands.json b/conductor/conductor/tests/unit/data/demands.json new file mode 100644 index 0000000..459a013 --- /dev/null +++ b/conductor/conductor/tests/unit/data/demands.json @@ -0,0 +1,30 @@ +{ + "demands": { + "vGMuxInfra": [ + { + "inventory_provider": "aai", + "inventory_type": "service", + "service_type": "vG_Mux", + "attributes": { + "customer-id": "some_company", + "orchestration-status": "Activated" + } + } + ], + "vG": [ + { + "inventory_provider": "aai", + "inventory_type": "service", + "service_type": "vG", + "attributes": { + "customer-id": "some_company", + "provisioning-status": "provisioned" + } + }, + { + "inventory_provider": "aai", + "inventory_type": "cloud" + } + ] + } +}
\ No newline at end of file diff --git a/conductor/conductor/tests/unit/data/test_service.py b/conductor/conductor/tests/unit/data/test_service.py new file mode 100644 index 0000000..b2c47be --- /dev/null +++ b/conductor/conductor/tests/unit/data/test_service.py @@ -0,0 +1,154 @@ +# +# ------------------------------------------------------------------------- +# Copyright (c) 2018 Intel Corporation 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 unittest + +import conductor.data.service as service +import mock +import stevedore +import yaml +from conductor.data.plugins.inventory_provider import extensions as ip_ext +from conductor.data.plugins.service_controller import extensions as sc_ext +from conductor.data.service import DataEndpoint +from oslo_config import cfg + + +class TestDataEndpoint(unittest.TestCase): + + def setUp(self): + ip_ext_manager = ( + ip_ext.Manager(cfg.CONF, 'conductor.inventory_provider.plugin')) + sc_ext_manager = ( + sc_ext.Manager(cfg.CONF, 'conductor.service_controller.plugin')) + self.data_ep = DataEndpoint(ip_ext_manager, sc_ext_manager) + + def tearDown(self): + pass + + def test_get_candidate_location(self): + req_json_file = './conductor/tests/unit/data/candidate_list.json' + req_json_candidate = json.loads(open(req_json_file).read()) + req_json = dict() + req_json['candidate'] = req_json_candidate['candidate_list'][0] + location = (32.897480, -97.040443) + self.assertEqual({'response': location, 'error': False}, + self.data_ep.get_candidate_location(None, req_json)) + req_json['candidate']['latitude'] = None + req_json['candidate']['longitude'] = None + self.assertEqual({'response': None, 'error': True}, + self.data_ep.get_candidate_location(None, + req_json)) + req_json['candidate'] = req_json_candidate['candidate_list'][1] + location = (40.7128, -74.0060) + self.assertEqual({'response': location, 'error': False}, + self.data_ep.get_candidate_location(None, req_json)) + + def test_get_candidate_zone(self): + req_json_file = './conductor/tests/unit/data/candidate_list.json' + req_json_candidate = json.loads(open(req_json_file).read()) + req_json = dict() + req_json['candidate'] = req_json_candidate['candidate_list'][0] + req_json['category'] = None + self.assertEqual({'response': None, 'error': True}, + self.data_ep.get_candidate_zone(None, req_json)) + req_json['category'] = 'region' + self.assertEqual({'response': 'DLLSTX55', 'error': False}, + self.data_ep.get_candidate_zone(None, req_json)) + req_json['category'] = 'complex' + self.assertEqual({'response': 'dalls_one', 'error': False}, + self.data_ep.get_candidate_zone(None, req_json)) + req_json['candidate'] = req_json_candidate['candidate_list'][1] + req_json['category'] = 'region' + self.assertEqual({'response': 'NYCNY55', 'error': False}, + self.data_ep.get_candidate_zone(None, req_json)) + + + @mock.patch.object(service.LOG, 'error') + @mock.patch.object(service.LOG, 'debug') + @mock.patch.object(stevedore.ExtensionManager, 'map_method') + def test_get_candidates_from_service(self, ext_mock, debug_mock, + error_mock): + req_json_file = './conductor/tests/unit/data/constraints.json' + req_json = yaml.safe_load(open(req_json_file).read()) + candidate_list = req_json['candidate_list'] + ext_mock.return_value = [candidate_list] + self.maxDiff = None + self.assertEqual(2, len( + self.data_ep.get_candidates_from_service(None, req_json))) + req_json['controller'] = 'APP-C' + self.assertEqual({'response': [], 'error': False}, + self.data_ep.get_candidates_from_service(None, + req_json)) + + def test_get_candidate_discard_set(self): + req_json_file = './conductor/tests/unit/data/constraints.json' + req_json = yaml.safe_load(open(req_json_file).read()) + value_attrib = 'complex_name' + value = req_json['properties']['evaluate'][value_attrib] + candidate_list = req_json['candidate_list'] + self.assertEqual(2, len(self.data_ep.get_candidate_discard_set(value, + candidate_list, + value_attrib))) + value_attrib = 'region' + value = req_json['properties']['evaluate'][value_attrib] + self.assertEqual(0, len(self.data_ep.get_candidate_discard_set(value, + candidate_list, + value_attrib))) + + @mock.patch.object(service.LOG, 'error') + @mock.patch.object(service.LOG, 'debug') + @mock.patch.object(service.LOG, 'info') + @mock.patch.object(stevedore.ExtensionManager, 'map_method') + @mock.patch.object(stevedore.ExtensionManager, 'names') + def test_get_candidates_by_attributes(self, ext_mock2, ext_mock1, + info_mock, debug_mock, error_mock): + req_json_file = './conductor/tests/unit/data/constraints.json' + req_json = yaml.safe_load(open(req_json_file).read()) + candidate_list = req_json['candidate_list'] + ext_mock1.return_value = [candidate_list] + ext_mock2.return_value = [None] + self.maxDiff = None + expected_response = {'response': [candidate_list[0]], 'error': False} + self.assertEqual(expected_response, + self.data_ep.get_candidates_by_attributes(None, + req_json)) + + @mock.patch.object(service.LOG, 'error') + @mock.patch.object(service.LOG, 'debug') + @mock.patch.object(service.LOG, 'info') + @mock.patch.object(stevedore.ExtensionManager, 'map_method') + def test_reslove_demands(self, ext_mock, info_mock, debug_mock, + error_mock): + req_json_file = './conductor/tests/unit/data/demands.json' + req_json = yaml.safe_load(open(req_json_file).read()) + ext_mock.return_value = [] + expected_response = {'response': {'resolved_demands': None}, + 'error': True} + self.assertEqual(expected_response, + self.data_ep.resolve_demands(None, req_json)) + return_value = req_json['demands']['vG'] + ext_mock.return_value = [return_value] + expected_response = {'response': {'resolved_demands': return_value}, + 'error': False} + self.assertEqual(expected_response, + self.data_ep.resolve_demands(None, req_json)) + + +if __name__ == "__main__": + unittest.main() diff --git a/conductor/pom.xml b/conductor/pom.xml index f12574a..db0f047 100644 --- a/conductor/pom.xml +++ b/conductor/pom.xml @@ -22,7 +22,7 @@ <parent> <groupId>org.onap.optf.has</groupId> <version>1.1.0-SNAPSHOT</version> - <artifactId>optf-has-root</artifactId> + <artifactId>optf-has</artifactId> </parent> <groupId>org.onap.optf.has</groupId> diff --git a/conductor/setup.cfg b/conductor/setup.cfg index b62c365..8e3fc56 100644 --- a/conductor/setup.cfg +++ b/conductor/setup.cfg @@ -3,7 +3,7 @@ name = of-has summary = ONAP Homing Service description-file = README.rst author = AT&T -author-email = jdandrea@research.att.com +author-email = ikram@research.att.com home-page = https://wiki.onap.org/pages/viewpage.action?pageId=16005528 classifier = Development Status :: 4 - Beta diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..dba217f --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,133 @@ +.. This work is licensed under a Creative Commons Attribution 4.0 International License. + +Homing API v1 +============= + +*Updated: 28 Feb 2018* + +This document describes the Homing API, provided by the Homing and Allocation service (Conductor). +It is a work in progress and subject to frequent revision. + +General API Information +======================= + +Authenticated calls that target a known URI but that use an HTTP method +the implementation does not support return a 405 Method Not Allowed +status. In addition, the HTTP OPTIONS method is supported for each known +URI. In both cases, the Allow response header indicates the supported +HTTP methods. See the API Errors section for more information about the +error response structure. + +API versions +============ + +List all Homing API versions +---------------------------- + +**GET** ``/``\ F + +**Normal response codes:** 200 + +.. code:: json + + { + "versions": [ + { + "status": "EXPERIMENTAL", + "id": "v1", + "updated": "2016-11-01T00:00:00Z", + "media-types": [ + { + "base": "application/json", + "type": "application/vnd.onap.homing-v1+json" + } + ], + "links": [ + { + "href": "http://has.ip/v1", + "rel": "self" + }, + { + "href": "http://has.url/", + "type": "text/html", + "rel": "describedby" + } + ] + } + ] + } + +This operation does not accept a request body. + +Plans +===== + +Create a plan +------------- + +**POST** ``/v1/plans`` + +- **Normal response codes:** 201 +- **Error response codes:** badRequest (400), unauthorized (401), + internalServerError (500) + +Request an inventory plan for one or more related service demands. + +The request includes or references a declarative **template**, +consisting of: + +- **Parameters** that can be referenced like macros +- **Demands** for service made against inventory +- **Locations** that are common to the overall plan +- **Constraints** made against demands, resulting in a set of inventory + candidates +- **Optimizations** to further narrow down the remaining candidates + +The response contains an inventory **plan**, consisting of one or more +sets of recommended pairings of demands with an inventory candidate’s +attributes and region. + +Request Parameters +~~~~~~~~~~~~~~~~~~ + ++--------------------+------------+----------+------------------------+ +| Parameter | Style | Type | Description | ++====================+============+==========+========================+ +| ``name`` | plain | xsd:stri | A name for the new | +| (Optional) | | ng | plan. If a name is not | +| | | | provided, it will be | +| | | | auto-generated based | +| | | | on the homing | +| | | | template. This name | +| | | | must be unique within | +| | | | a given Conductor | +| | | | environment. When | +| | | | deleting a plan, its | +| | | | name will not become | +| | | | available for reuse | +| | | | until the deletion | +| | | | completes | +| | | | successfully. Must | +| | | | only contain letters, | +| | | | numbers, hypens, full | +| | | | stops, underscores, | +| | | | and tildes (RFC 3986, | +| | | | Section 2.3). This | +| | | | parameter is | +| | | | immutable. | ++--------------------+------------+----------+------------------------+ +| ``id`` (Optional) | plain | csapi:UU | The UUID of the plan. | +| | | ID | UUID is assigned by | +| | | | Conductor if no id is | +| | | | provided in the | +| | | | request. | ++--------------------+------------+----------+------------------------+ +| ``transaction_id`` | plain | csapi:UU | The transaction id | +| | | ID | assigned by SO. The | +| | | | logs should have this | +| | | | transaction id for | +| | | | tracking purposes. | ++--------------------+------------+----------+------------------------+ +| ``files`` | plain | xsd:dict | Supplies the contents | +| (Optional) | | | of files referenced. | ++--------------------+------------+----------+------------------------+
\ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 636f71e..2ea9c0f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,4 +6,5 @@ Optimization Framework: Homing and Allocation .. toctree:: :maxdepth: 4 + api release-notes/index @@ -26,7 +26,7 @@ </parent> <groupId>org.onap.optf.has</groupId> - <artifactId>optf-has-root</artifactId> + <artifactId>optf-has</artifactId> <name>optf-has</name> <version>1.1.0-SNAPSHOT</version> @@ -37,6 +37,19 @@ <!--<module>docs</module>--> </modules> + <properties> + <sonar.sourceEncoding>UTF-8</sonar.sourceEncoding> + <sonar.sources>conductor/conductor</sonar.sources> + <sonar.tests>conductor/conductor/tests</sonar.tests> + <sonar.python.coverage.reportPath>conductor/cover/coverage.xml</sonar.python.coverage.reportPath> + <sonar.language>py</sonar.language> + <sonar.pluginname>python</sonar.pluginname> + <sonar.inclusions>**/**.py</sonar.inclusions> + <sonar.exclusions>**/tests/**,setup.py,**/lib/**</sonar.exclusions> + <sonar.test.inclusions>**/tests/**.py</sonar.test.inclusions> + <sonar.test.exclusions>**/**.py,setup.py,**/lib/**</sonar.test.exclusions> + </properties> + <build> <plugins> <plugin> |