summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYing Ruoyu <ruoyu.ying@intel.com>2018-03-07 22:21:40 +0800
committerDileep Ranganathan <dileep.ranganathan@intel.com>2018-03-07 10:44:17 -0800
commit6ac2dff39d6edb6c411a641036f474bb2e493f5b (patch)
tree3f6d9a02c4fb9138ca3cc4d1d24c597432e45013
parentda17dd41a33cf488d16a2adedbca5c81cb3f75fc (diff)
Modified unit tests for Controller Module
Unit test for translator.py, rpc.py and translator_svc.py in conductor/controller Change-Id: I382078152d7708b6946b68902151529dcf1f3be4 Issue-ID: OPTFRA-69 Signed-off-by: Ying Ruoyu <ruoyu.ying@intel.com>
-rw-r--r--conductor/conductor/tests/data/some_template.yaml81
-rw-r--r--conductor/conductor/tests/unit/controller/__init__.py0
-rw-r--r--conductor/conductor/tests/unit/controller/test_rpc.py120
-rw-r--r--conductor/conductor/tests/unit/controller/test_translator.py268
-rw-r--r--conductor/conductor/tests/unit/controller/test_translator_svc.py91
5 files changed, 560 insertions, 0 deletions
diff --git a/conductor/conductor/tests/data/some_template.yaml b/conductor/conductor/tests/data/some_template.yaml
new file mode 100644
index 0000000..f37212f
--- /dev/null
+++ b/conductor/conductor/tests/data/some_template.yaml
@@ -0,0 +1,81 @@
+#Homing Specification Version
+homing_template_version: 2017-10-10
+
+# Runtime order Parameters
+parameters:
+ service_name: Residential vCPE
+ service_id: vcpe_service_id
+ customer_lat: 32.897480
+ customer_long: -97.040443
+
+# List of geographical locations
+locations:
+ customer_loc:
+ latitude: {get_param: customer_lat}
+ longitude: {get_param: customer_long}
+
+# List of VNFs (demands) to be homed
+demands:
+ vGMuxInfra:
+ - inventory_provider: aai
+ inventory_type: service
+ attributes:
+ equipment_type: vG_Mux
+ customer_id: some_company
+ excluded_candidates:
+ - candidate_id: 1ac71fb8-ad43-4e16-9459-c3f372b8236d
+ existing_placement:
+ - candidate_id: 21d5f3e8-e714-4383-8f99-cc480144505a
+ vG:
+ - inventory_provider: aai
+ inventory_type: service
+ attributes:
+ equipment_type: vG
+ modelId: vG_model_id
+ customer_id: some_company
+ excluded_candidates:
+ - candidate_id: 1ac71fb8-ad43-4e16-9459-c3f372b8236d
+ existing_placement:
+ - candidate_id: 21d5f3e8-e714-4383-8f99-cc480144505a
+ - inventory_provider: aai
+ inventory_type: cloud
+
+# List of homing policies (constraints)
+constraints:
+ # distance constraint
+ - constraint_vgmux_customer:
+ type: distance_to_location
+ demands: [vGMuxInfra]
+ properties:
+ distance: < 100 km
+ location: customer_loc
+ # cloud region co-location constraint
+ - colocation:
+ type: zone
+ demands: [vGMuxInfra, vG]
+ properties:
+ qualifier: same
+ category: region
+ # platform capability constraint
+ - numa_cpu_pin_capabilities:
+ type: attribute
+ demands: [vG]
+ properties:
+ evaluate:
+ vcpu_pinning: True
+ numa_topology: numa_spanning
+ # cloud provider constraint
+ - cloud_version_capabilities:
+ type: attribute
+ demands: [vGMuxInfra]
+ properties:
+ evaluate:
+ cloud_version: 1.11.84
+ cloud_provider: AWS
+
+# Objective function to minimize
+optimization:
+ minimize:
+ sum:
+ - {distance_between: [customer_loc, vGMuxInfra]}
+ - {distance_between: [customer_loc, vG]}
diff --git a/conductor/conductor/tests/unit/controller/__init__.py b/conductor/conductor/tests/unit/controller/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/conductor/conductor/tests/unit/controller/__init__.py
diff --git a/conductor/conductor/tests/unit/controller/test_rpc.py b/conductor/conductor/tests/unit/controller/test_rpc.py
new file mode 100644
index 0000000..1039468
--- /dev/null
+++ b/conductor/conductor/tests/unit/controller/test_rpc.py
@@ -0,0 +1,120 @@
+#
+# ------------------------------------------------------------------------
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+"""Test classes for rpc"""
+
+import unittest
+import uuid
+
+from conductor.controller.rpc import ControllerRPCEndpoint as rpc
+from conductor import service
+from conductor.common.models import plan
+from conductor.common.music.model import base
+from conductor.common.music import api
+from oslo_config import cfg
+from mock import patch
+
+
+def plan_prepare(conf):
+ music = api.API()
+ music.keyspace_create(keyspace=conf.keyspace)
+ plan_tmp = base.create_dynamic_model(
+ keyspace=conf.keyspace, baseclass=plan.Plan, classname="Plan")
+ return plan_tmp
+
+
+class TestRPCNoException(unittest.TestCase):
+ def setUp(self):
+ cfg.CONF.set_override('timeout', 10, 'controller')
+ cfg.CONF.set_override('limit', 1, 'controller')
+ cfg.CONF.set_override('keyspace', 'conductor')
+ cfg.CONF.set_override('mock', True, 'music_api')
+ conf = cfg.CONF
+ plan_class = plan_prepare(conf)
+ self.r = rpc(conf, plan_class)
+ self._cvx = ""
+ self._arg = {
+ "name": str(uuid.uuid4()),
+ "timeout": conf,
+ "limit": conf,
+ "template": None
+ }
+ self.plan_expected = {
+ "plan": {
+ "name": "null",
+ "id": "null",
+ "status": "null"
+ }
+ }
+ self.plan_mock = []
+ element = plan_prepare(conf)
+ setattr(element, "name", "null")
+ setattr(element, "id", "null")
+ setattr(element, "status", "null")
+ setattr(element, "message", "null")
+ e = {'recommendations': 'null'}
+ setattr(element, "solution", e)
+ self.plan_mock.append(element)
+ self.the_plan_expected = [{
+ "name": "null",
+ "id": "null",
+ "status": "null",
+ "message": "null",
+ "recommendations": "null"
+ }]
+
+ def test_plan_creation(self):
+ a_arg = []
+ b_arg = []
+ rtn = self.r.plan_create(self._cvx, self._arg)
+ for k in sorted(rtn.get('response')):
+ a_arg.append(k)
+ for key in sorted(self.plan_expected):
+ b_arg.append(key)
+ self.assertEquals(rtn.get('error'), False)
+ self.assertEquals(a_arg, b_arg)
+ for k in sorted(rtn.get('response').get('plan')):
+ a_arg.append(k)
+ for key in sorted(self.plan_expected.get('plan')):
+ b_arg.append(key)
+ self.assertEquals(a_arg, b_arg)
+
+ @patch('conductor.common.music.model.search.Query.all')
+ def test_plan_get_same_schema(self, mock_query):
+ _id = {}
+ mock_query.return_value = self.plan_mock
+ rtn_get = self.r.plans_get(self._cvx, _id)
+ plans = rtn_get.get('response').get('plans')
+ self.assertEquals(plans, self.the_plan_expected)
+ self.assertFalse(rtn_get.get('error'))
+
+ @patch('conductor.common.music.model.search.Query.all')
+ @patch('conductor.common.music.model.base.Base.delete')
+ def test_plans_delete(self, mock_delete, mock_call):
+ _id = {}
+ mock_call.return_value = self.plan_mock
+ rtn = self.r.plans_delete(self._cvx, _id)
+ self.assertEquals(rtn.get('response'), {})
+ self.assertFalse(rtn.get('error'))
+
+ def tearDown(self):
+ patch.stopall()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/conductor/conductor/tests/unit/controller/test_translator.py b/conductor/conductor/tests/unit/controller/test_translator.py
new file mode 100644
index 0000000..2dbee00
--- /dev/null
+++ b/conductor/conductor/tests/unit/controller/test_translator.py
@@ -0,0 +1,268 @@
+#
+# ------------------------------------------------------------------------
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+"""Test classes for translator"""
+
+import os
+import yaml
+import uuid
+import unittest
+
+from conductor.controller.translator import Translator
+from conductor.controller.translator import TranslatorException
+from conductor import __file__ as conductor_root
+from oslo_config import cfg
+from mock import patch
+
+
+def get_template():
+ template_name = 'some_template'
+ path = os.path.abspath(conductor_root)
+ dir_path = os.path.dirname(path)
+ template_file = dir_path + '/tests/data/' + template_name + '.yaml'
+ fd = open(template_file, "r")
+ template = yaml.load(fd)
+ return template
+
+
+class TestNoExceptionTranslator(unittest.TestCase):
+ @patch('conductor.common.music.model.base.Base.table_create')
+ def setUp(self, mock_table_create):
+ cfg.CONF.set_override('keyspace', 'conductor')
+ cfg.CONF.set_override('keyspace', 'conductor_rpc', 'messaging_server')
+ cfg.CONF.set_override('concurrent', True, 'controller')
+ cfg.CONF.set_override('mock', True, 'music_api')
+ conf = cfg.CONF
+ self.Translator = Translator(
+ conf, 'some_template', str(uuid.uuid4()), get_template())
+
+ def test_create_correct_components(self):
+ self.Translator.create_components()
+ self.assertIsNotNone(self.Translator._version)
+ self.assertIsNotNone(self.Translator._parameters)
+ self.assertIsNotNone(self.Translator._constraints)
+ self.assertIsNotNone(self.Translator._demands)
+ self.assertIsNotNone(self.Translator._locations)
+ self.assertIsNotNone(self.Translator._reservations)
+ self.assertIsNotNone(self.Translator._optmization)
+ self.assertIsInstance(self.Translator._version, str)
+
+ def test_error_version_validation(self):
+ self.Translator._version = "2016-11-02"
+ self.assertRaises(TranslatorException,
+ self.Translator.validate_components, )
+
+ def test_error_format_validation(self):
+ self.Translator._version = "2016-11-01"
+ self.Translator._locations = ""
+ self.Translator._demands = ""
+ self.Translator._constraints = ""
+ self.Translator._reservations = ""
+ self.Translator._optmization = ""
+ self.Translator._parameters = ""
+ self.assertRaises(TranslatorException,
+ self.Translator.validate_components, )
+
+ def test_validation_complete(self):
+ self.Translator._demands = {'vG': ''}
+ self.Translator._version = "2016-11-01"
+ self.Translator._locations = {'custom_loc': {'longitude': ''}}
+ self.Translator._constraints = {'vG': {
+ 'demands': 'vG',
+ 'properties': {'location': 'custom_loc'}}}
+ self.Translator._parameters = {}
+ self.Translator._optmization = {}
+ self.Translator._reservations = {}
+ self.Translator.validate_components()
+ self.assertTrue(self.Translator._valid)
+
+ def test_parse_parameter(self):
+ self.Translator.create_components()
+ rtn = self.Translator._parse_parameters(
+ self.Translator._locations, "locations")
+ location = {'customer_loc': {
+ 'latitude': 32.89748, 'longitude': -97.040443}}
+ self.assertEquals(rtn, location)
+
+ @patch('conductor.common.music.messaging.component.RPCClient.call')
+ def test_parse_locations(self, mock_call):
+ locations = {'customer_loc': {
+ 'latitude': 32.89748, 'longitude': -97.040443}
+ }
+ mock_call.return_value = {'resolved_location': {
+ 'latitude': 32.89748, 'longitude': -97.040443}}
+ self.assertEquals(
+ self.Translator.parse_locations(locations), locations)
+
+ def test_parse_error_format_demands(self):
+ demands = ""
+ self.assertRaises(TranslatorException,
+ self.Translator.parse_demands, demands)
+
+ @patch('conductor.common.music.messaging.component.RPCClient.call')
+ def test_parse_demands_without_candidate(self, mock_call):
+ demands = {
+ "vGMuxInfra": [{
+ "inventory_provider": "aai",
+ "inventory_type": "service",
+ "customer_id": "some_company",
+ "service_type": "5G",
+ "excluded_candidates": [{
+ "candidate_id": "1ac71fb8-ad43-4e16-9459-c3f372b8236d"
+ }],
+ "required_candidates": [{
+ "candidate_id": "1a9983b8-0o43-4e16-9947-c3f37234536d"
+ }]
+ }
+ ]}
+ self.Translator._plan_id = ""
+ self.Translator._plan_name = ""
+ mock_call.return_value = {'resolved_demands': {"vGMuxInfra": [{
+ "inventory_provider": "aai",
+ "inventory_type": "service",
+ "customer_id": "some_company",
+ "service_type": "5G",
+ "excluded_candidates": [{
+ "candidate_id:1ac71fb8-ad43-4e16-9459-c3f372b8236d"
+ }],
+ "required_candidates": [{
+ "candidate_id": "1a9983b8-0o43-4e16-9947-c3f37234536d"}]
+
+ }]
+ }}
+ rtn = {'vGMuxInfra': {'candidates': [{
+ 'customer_id': 'some_company',
+ 'excluded_candidates': [set([
+ 'candidate_id:1ac71fb8-ad43-4e16-9459-c3f372b8236d'])],
+ 'inventory_provider': 'aai',
+ 'inventory_type': 'service',
+ 'required_candidates': [{
+ 'candidate_id': '1a9983b8-0o43-4e16-9947-c3f37234536d'
+ }],
+ 'service_type': '5G'}]}}
+
+ self.assertEquals(self.Translator.parse_demands(demands), rtn)
+
+ def test_parse_constraints(self):
+ constraints = {'constraint_loc': {
+ 'type': 'distance_to_location',
+ 'demands': ['vG'],
+ 'properties': {'distance': '< 100 km',
+ 'location': 'custom_loc'}}}
+ rtn = {'constraint_loc_vG': {
+ 'demands': 'vG',
+ 'name': 'constraint_loc',
+ 'properties': {'distance': {'operator': '<',
+ 'units': 'km',
+ 'value': 100.0},
+ 'location': 'custom_loc'},
+ 'type': 'distance_to_location'}}
+ self.assertEquals(self.Translator.parse_constraints(constraints), rtn)
+
+ # TODO(ruoyu)
+ @patch('conductor.controller.translator.Translator.create_components')
+ def parse_optimization(self, mock_create):
+ args = ['customer_loc', 'vGMuxInfra']
+ func = 'distance_between'
+ expected_parse = {
+ "goal": "min",
+ "operation": "sum",
+ "operands": [{"operation": "product",
+ "weight": 1.0,
+ "function": func,
+ "function_param": args}]
+ }
+ opt = {'minimize': {
+ 'sum': [{
+ 'distance_between': ['customer_loc', 'vGMuxInfra']}, {
+ 'distance_between': ['customer_loc', 'vG']}]}}
+ self.Translator._demands = {'vG': '',
+ 'vGMuxInfra': '',
+ 'customer_loc': ''}
+ self.Translator._locations = {'vG': '',
+ 'vGMuxInfra': '',
+ 'customer_loc': ''}
+ self.assertEquals(
+ self.Translator.parse_optimization(
+ opt), expected_parse)
+
+ @patch('conductor.controller.translator.Translator.create_components')
+ def test_parse_reservation(self, mock_create):
+ expected_resv = {'counter': 0, 'demands': {
+ 'instance_vG': {'demands': {'vG': 'null'},
+ 'properties': {},
+ 'name': 'instance',
+ 'demand': 'vG'}}}
+ self.Translator._demands = {'vG': 'null'}
+ resv = {
+ 'instance': {'demands': {'vG': 'null'}}
+ }
+ self.assertEquals(
+ self.Translator.parse_reservations(resv), expected_resv)
+
+ @patch('conductor.controller.translator.Translator.parse_constraints')
+ @patch('conductor.controller.translator.Translator.parse_reservations')
+ @patch('conductor.controller.translator.Translator.parse_demands')
+ @patch('conductor.controller.translator.Translator.parse_optimization')
+ @patch('conductor.controller.translator.Translator.parse_locations')
+ def test_do_translation(self, mock_loc, mock_opt,
+ mock_dmd, mock_resv, mock_cons):
+ expected_format = {
+ "conductor_solver": {
+ "version": '',
+ "plan_id": '',
+ "locations": {},
+ "request_type": '',
+ "demands": {},
+ "constraints": {},
+ "objective": {},
+ "reservations": {},
+ }
+ }
+ self.Translator._valid = True
+ self.Translator._version = ''
+ self.Translator._plan_id = ''
+ self.Translator._parameters = {}
+ self.Translator._locations = {}
+ self.Translator._demands = {}
+ self.Translator._constraints = {}
+ self.Translator._optmization = {}
+ self.Translator._reservations = {}
+ mock_loc.return_value = {}
+ mock_resv.return_value = {}
+ mock_dmd.return_value = {}
+ mock_opt.return_value = {}
+ mock_cons.return_value = {}
+ self.Translator.do_translation()
+ self.assertEquals(self.Translator._translation, expected_format)
+
+ @patch('conductor.controller.translator.Translator.create_components')
+ @patch('conductor.controller.translator.Translator.validate_components')
+ @patch('conductor.controller.translator.Translator.do_translation')
+ @patch('conductor.controller.translator.Translator.parse_parameters')
+ def test_translate(self, mock_parse, mock_do_trans,
+ mock_valid, mock_create):
+ self.Translator.translate()
+ self.assertEquals(self.Translator._ok, True)
+
+ def tearDown(self):
+ patch.stopall()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/conductor/conductor/tests/unit/controller/test_translator_svc.py b/conductor/conductor/tests/unit/controller/test_translator_svc.py
new file mode 100644
index 0000000..051e9d3
--- /dev/null
+++ b/conductor/conductor/tests/unit/controller/test_translator_svc.py
@@ -0,0 +1,91 @@
+#
+# ------------------------------------------------------------------------
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+"""Test classes for translator_svc"""
+
+import unittest
+import uuid
+
+from mock import patch
+from mock import PropertyMock
+from conductor.controller.translator_svc import TranslatorService
+from conductor.common.models import plan
+from conductor.common.music import api
+from conductor.common.music.model import base
+from oslo_config import cfg
+
+
+def plan_prepare(conf):
+ music = api.API()
+ music.keyspace_create(keyspace=conf.keyspace)
+ plan_tmp = base.create_dynamic_model(
+ keyspace=conf.keyspace, baseclass=plan.Plan, classname="Plan")
+ return plan_tmp
+
+
+class TestTranslatorServiceNoException(unittest.TestCase):
+ def setUp(self):
+ cfg.CONF.set_override('polling_interval', 1, 'controller')
+ cfg.CONF.set_override('keyspace', 'conductor')
+ cfg.CONF.set_override('timeout', 10, 'controller')
+ cfg.CONF.set_override('limit', 1, 'controller')
+ cfg.CONF.set_override('concurrent', True, 'controller')
+ cfg.CONF.set_override('keyspace',
+ 'conductor_rpc', 'messaging_server')
+ cfg.CONF.set_override('mock', True, 'music_api')
+ self.conf = cfg.CONF
+ self.Plan = plan_prepare(self.conf)
+ kwargs = self.Plan
+ name = str(uuid.uuid4())
+ timeout = self.conf.controller.timeout
+ recommend_max = self.conf.controller.limit
+ template = None
+ status = self.Plan.TEMPLATE
+ self.mock_plan = self.Plan(name, timeout, recommend_max, template,
+ status=status)
+ self.translator_svc = TranslatorService(
+ worker_id=1, conf=self.conf, plan_class=kwargs)
+ self.translator_svc.music.keyspace_create(keyspace=self.conf.keyspace)
+
+ #TODO(ruoyu)
+ @patch('conductor.controller.translator.Translator.ok')
+ def translate_complete(self, mock_ok_func):
+ with patch('conductor.controller.translator.Translator.ok',
+ new_callable=PropertyMock) as mock_ok:
+ mock_ok.return_value = True
+ mock_ok_func.return_value = True
+ self.translator_svc.translate(self.mock_plan)
+ self.assertEquals(self.mock_plan.status, 'translated')
+
+ # TODO(ruoyu)
+ @patch('conductor.controller.translator.Translator.translate')
+ @patch('conductor.controller.translator.Translator.error_message')
+ def translate_error(self, mock_error, mock_trns):
+ with patch('conductor.controller.translator.Translator.ok',
+ new_callable=PropertyMock) as mock_ok:
+ mock_ok.return_value = False
+ mock_error.return_value = 'error'
+ self.translator_svc.translate(self.mock_plan)
+ self.assertEquals(self.mock_plan.status, 'error')
+
+ def tearDown(self):
+ patch.stopall()
+
+
+if __name__ == '__main__':
+ unittest.main()