summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDileep Ranganathan <dileep.ranganathan@intel.com>2018-03-20 07:47:27 -0700
committerDileep Ranganathan <dileep.ranganathan@intel.com>2018-03-28 03:35:55 -0700
commit39f63ef31ba3f0dc0db3ee8d5ff502a715702e1a (patch)
tree333745fb4722d6c5fe27b3f6c1d3e21932087c00
parent1ad66ccc583b778b9b1aef8aefb4b13362f6045f (diff)
Multicloud vim controller plugin
Implemented Multicloud vim controller plugin Updated translator to parse vim_fit constraint Implemented vim_fit constraint type Implemented RPC for check_vim_capacity Reordered constraint rank Added unit tests Change-Id: I5f01cf8fbefbb4b53e4370c5c6b43f72897e62bd Issue-ID: OPTFRA-148 Signed-off-by: Dileep Ranganathan <dileep.ranganathan@intel.com>
-rw-r--r--conductor/conductor/conf/vim_controller.py33
-rw-r--r--conductor/conductor/controller/translator.py16
-rw-r--r--conductor/conductor/data/plugins/vim_controller/__init__.py0
-rw-r--r--conductor/conductor/data/plugins/vim_controller/base.py37
-rw-r--r--conductor/conductor/data/plugins/vim_controller/extensions.py45
-rw-r--r--conductor/conductor/data/plugins/vim_controller/multicloud.py144
-rw-r--r--conductor/conductor/data/service.py53
-rw-r--r--conductor/conductor/opts.py6
-rw-r--r--conductor/conductor/solver/optimizer/constraints/vim_fit.py60
-rwxr-xr-xconductor/conductor/solver/request/parser.py17
-rw-r--r--conductor/conductor/solver/utils/constraint_engine_interface.py17
-rw-r--r--conductor/conductor/tests/unit/controller/test_translator.py62
-rw-r--r--conductor/conductor/tests/unit/data/candidate_list.json6
-rw-r--r--conductor/conductor/tests/unit/data/hpa_constraints.json20
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/test_multicloud.py96
-rw-r--r--conductor/conductor/tests/unit/data/test_service.py42
-rw-r--r--conductor/conductor/tests/unit/solver/candidate_list.json6
-rw-r--r--conductor/conductor/tests/unit/solver/hpa_constraints.json20
-rw-r--r--conductor/conductor/tests/unit/solver/test_vim_fit.py82
-rw-r--r--conductor/setup.cfg3
20 files changed, 750 insertions, 15 deletions
diff --git a/conductor/conductor/conf/vim_controller.py b/conductor/conductor/conf/vim_controller.py
new file mode 100644
index 0000000..91eb079
--- /dev/null
+++ b/conductor/conductor/conf/vim_controller.py
@@ -0,0 +1,33 @@
+#
+# -------------------------------------------------------------------------
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+
+
+from oslo_config import cfg
+
+from conductor.i18n import _
+
+VIM_CONTROLLER_EXT_MANAGER_OPTS = [
+ cfg.ListOpt('extensions',
+ default=['multicloud'],
+ help=_('Extensions list to use')),
+]
+
+
+def register_extension_manager_opts(cfg=cfg.CONF):
+ cfg.register_opts(VIM_CONTROLLER_EXT_MANAGER_OPTS, 'vim_controller')
diff --git a/conductor/conductor/controller/translator.py b/conductor/conductor/controller/translator.py
index dbff2d2..724b068 100644
--- a/conductor/conductor/controller/translator.py
+++ b/conductor/conductor/controller/translator.py
@@ -22,17 +22,16 @@ import datetime
import json
import os
import uuid
-import yaml
-from oslo_config import cfg
-from oslo_log import log
import six
-
+import yaml
from conductor import __file__ as conductor_root
-from conductor.common.music import messaging as music_messaging
-from conductor.common import threshold
from conductor import messaging
from conductor import service
+from conductor.common import threshold
+from conductor.common.music import messaging as music_messaging
+from oslo_config import cfg
+from oslo_log import log
LOG = log.getLogger(__name__)
@@ -95,6 +94,11 @@ CONSTRAINTS = {
'category': ['disaster', 'region', 'complex', 'country',
'time', 'maintenance']},
},
+ 'vim_fit': {
+ 'split': True,
+ 'required': ['controller'],
+ 'optional': ['request'],
+ },
}
diff --git a/conductor/conductor/data/plugins/vim_controller/__init__.py b/conductor/conductor/data/plugins/vim_controller/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/conductor/conductor/data/plugins/vim_controller/__init__.py
diff --git a/conductor/conductor/data/plugins/vim_controller/base.py b/conductor/conductor/data/plugins/vim_controller/base.py
new file mode 100644
index 0000000..6f35924
--- /dev/null
+++ b/conductor/conductor/data/plugins/vim_controller/base.py
@@ -0,0 +1,37 @@
+#
+# -------------------------------------------------------------------------
+# 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 abc
+
+from oslo_log import log
+import six
+
+from conductor.data.plugins import base
+
+LOG = log.getLogger(__name__)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class VimControllerBase(base.DataPlugin):
+ """Base class for Vim Controller plugins"""
+
+ @abc.abstractmethod
+ def name(self):
+ """Return human-readable name."""
+ pass
diff --git a/conductor/conductor/data/plugins/vim_controller/extensions.py b/conductor/conductor/data/plugins/vim_controller/extensions.py
new file mode 100644
index 0000000..25887cf
--- /dev/null
+++ b/conductor/conductor/data/plugins/vim_controller/extensions.py
@@ -0,0 +1,45 @@
+#
+# -------------------------------------------------------------------------
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+
+from oslo_log import log
+import stevedore
+
+from conductor.conf import vim_controller
+from conductor.i18n import _LI
+
+LOG = log.getLogger(__name__)
+
+vim_controller.register_extension_manager_opts()
+
+
+class Manager(stevedore.named.NamedExtensionManager):
+ """Manage Vim Controller extensions."""
+
+ def __init__(self, conf, namespace):
+ super(Manager, self).__init__(
+ namespace, conf.vim_controller.extensions,
+ invoke_on_load=True, name_order=True)
+ LOG.info(_LI("Loaded Vim controller extensions: %s"), self.names())
+
+ def initialize(self):
+ """Initialize enabled Vim controller extensions."""
+ for extension in self.extensions:
+ LOG.info(_LI("Initializing Vim controller extension '%s'"),
+ extension.name)
+ extension.obj.initialize()
diff --git a/conductor/conductor/data/plugins/vim_controller/multicloud.py b/conductor/conductor/data/plugins/vim_controller/multicloud.py
new file mode 100644
index 0000000..cdc6cde
--- /dev/null
+++ b/conductor/conductor/data/plugins/vim_controller/multicloud.py
@@ -0,0 +1,144 @@
+#
+# -------------------------------------------------------------------------
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+
+'''Multicloud Vim controller plugin'''
+
+import time
+import uuid
+
+from conductor.common import rest
+from conductor.data.plugins.vim_controller import base
+from conductor.i18n import _LE, _LI
+from oslo_config import cfg
+from oslo_log import log
+
+LOG = log.getLogger(__name__)
+
+CONF = cfg.CONF
+
+MULTICLOUD_OPTS = [
+ cfg.StrOpt('server_url',
+ default='http://msb.onap.org/api/multicloud',
+ help='Base URL for Multicloud without a trailing slash.'),
+ cfg.StrOpt('multicloud_rest_timeout',
+ default=30,
+ help='Timeout for Multicloud Rest Call'),
+ cfg.StrOpt('multicloud_retries',
+ default=3,
+ help='Number of retry for Multicloud Rest Call'),
+ cfg.StrOpt('server_url_version',
+ default='v0',
+ help='The version of Multicloud API.'),
+]
+
+CONF.register_opts(MULTICLOUD_OPTS, group='multicloud')
+
+
+class MULTICLOUD(base.VimControllerBase):
+ """Multicloud Vim controller"""
+
+ def __init__(self):
+ """Initializer"""
+ self.conf = CONF
+ self.base = self.conf.multicloud.server_url.rstrip('/')
+ self.version = self.conf.multicloud.server_url_version.rstrip('/')
+ self.timeout = self.conf.multicloud.multicloud_rest_timeout
+ self.retries = self.conf.multicloud.multicloud_retries
+
+ def initialize(self):
+ LOG.info(_LI("**** Initializing Multicloud Vim controller *****"))
+ self._init_rest_request()
+
+ def name(self):
+ """Return human-readable name."""
+ return "MultiCloud"
+
+ def _request(self, method='get', path='/', data=None,
+ context=None, value=None):
+ """Performs HTTP request."""
+ headers = {
+ 'X-FromAppId': 'CONDUCTOR',
+ 'X-TransactionId': str(uuid.uuid4()),
+ }
+ kwargs = {
+ "method": method,
+ "path": path,
+ "headers": headers,
+ "data": data,
+ }
+
+ start_time = time.time()
+ response = self.rest.request(**kwargs)
+ elapsed = time.time() - start_time
+ LOG.debug("Total time for Multicloud request "
+ "({0:}: {1:}): {2:.3f} sec".format(context, value, elapsed))
+
+ if response is None:
+ LOG.error(_LE("No response from Multicloud ({}: {})").
+ format(context, value))
+ elif response.status_code != 200:
+ LOG.error(_LE("Multicloud request ({}: {}) returned HTTP "
+ "status {} {}, link: {}{}").
+ format(context, value,
+ response.status_code, response.reason,
+ self.base, path))
+ return response
+
+ def _init_rest_request(self):
+
+ kwargs = {
+ "server_url": self.base,
+ "retries": self.retries,
+ "log_debug": self.conf.debug,
+ "read_timeout": self.timeout,
+ }
+ self.rest = rest.REST(**kwargs)
+
+ def check_vim_capacity(self, vim_request):
+ LOG.debug("Invoking check_vim_capacity api")
+ path = '/{}/{}'.format(self.version, 'check_vim_capacity')
+
+ data = {}
+ data['vCPU'] = vim_request['vCPU']
+ data['Memory'] = vim_request['Memory']['quantity']
+ data['Storage'] = vim_request['Storage']['quantity']
+ data['VIMs'] = vim_request['VIMs']
+ response = self._request('post', path=path, data=data,
+ context="vim capacity", value="all")
+ LOG.debug("Response check_vim_capacity api - {}".format(response))
+ if response is None or response.status_code != 200:
+ return None
+
+ body = response.json()
+
+ if body:
+ vims = body.get("VIMs")
+ if vims:
+ return vims
+ else:
+ LOG.error(_LE(
+ "Unable to get VIMs with cpu-{}, memory-{}, disk-{}")
+ .format(data['vCPU'],
+ data['Memory'],
+ data['Storage']))
+ return None
+ else:
+ LOG.error(_LE("Unable to get VIMs from Multicloud with "
+ "requirement {}").format(data))
+ return None
diff --git a/conductor/conductor/data/service.py b/conductor/conductor/data/service.py
index 9617217..4948656 100644
--- a/conductor/conductor/data/service.py
+++ b/conductor/conductor/data/service.py
@@ -27,6 +27,7 @@ from conductor.common.music import messaging as music_messaging
from conductor.common.utils import conductor_logging_util as log_util
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.plugins.vim_controller import extensions as vc_ext
from conductor.i18n import _LE, _LI, _LW
from oslo_config import cfg
from oslo_log import log
@@ -77,6 +78,9 @@ class DataServiceLauncher(object):
self.ip_ext_manager = (
ip_ext.Manager(conf, 'conductor.inventory_provider.plugin'))
self.ip_ext_manager.initialize()
+ self.vc_ext_manager = (
+ vc_ext.Manager(conf, 'conductor.vim_controller.plugin'))
+ self.vc_ext_manager.initialize()
self.sc_ext_manager = (
sc_ext.Manager(conf, 'conductor.service_controller.plugin'))
self.sc_ext_manager.initialize()
@@ -87,6 +91,7 @@ class DataServiceLauncher(object):
topic = "data"
target = music_messaging.Target(topic=topic)
endpoints = [DataEndpoint(self.ip_ext_manager,
+ self.vc_ext_manager,
self.sc_ext_manager), ]
flush = not self.conf.data.concurrent
kwargs = {'transport': transport,
@@ -101,9 +106,10 @@ class DataServiceLauncher(object):
class DataEndpoint(object):
- def __init__(self, ip_ext_manager, sc_ext_manager):
+ def __init__(self, ip_ext_manager, vc_ext_manager, sc_ext_manager):
self.ip_ext_manager = ip_ext_manager
+ self.vc_ext_manager = vc_ext_manager
self.sc_ext_manager = sc_ext_manager
self.plugin_cache = {}
@@ -491,6 +497,51 @@ class DataEndpoint(object):
self.ip_ext_manager.names()[0]))
return {'response': candidate_list, 'error': error}
+ def get_candidates_with_vim_capacity(self, ctx, arg):
+ '''
+ RPC for getting candidates with vim capacity
+ :param ctx: context
+ :param arg: contains input passed from client side for RPC call
+ :return: response candidate_list with with required vim capacity
+ '''
+ error = False
+ candidate_list = arg["candidate_list"]
+ vim_request = arg["request"]
+ vim_list = set()
+ discard_set = set()
+ for candidate in candidate_list:
+ if candidate["inventory_type"] == "cloud":
+ vim_list.add(candidate['vim-id'])
+
+ vim_request['VIMs'] = list(vim_list)
+ vims_result = self.vc_ext_manager.map_method(
+ 'check_vim_capacity',
+ vim_request
+ )
+
+ if vims_result and len(vims_result) > 0:
+ vims_set = set(vims_result[0])
+ for candidate in candidate_list:
+ # perform this check only for cloud candidates
+ if candidate["inventory_type"] == "cloud":
+ if candidate['vim-id'] not in vims_set:
+ discard_set.add(candidate.get("candidate_id"))
+
+ # return candidates not in discard set
+ candidate_list[:] = [c for c in candidate_list
+ if c['candidate_id'] not in discard_set]
+ else:
+ error = True
+ LOG.warn(_LI(
+ "Multicloud did not respond properly to request: {}".format(
+ vim_request)))
+
+ LOG.info(_LI(
+ "Candidates with with vim capacity: {}, vim controller: "
+ "{}").format(candidate_list, self.vc_ext_manager.names()[0]))
+
+ return {'response': candidate_list, 'error': error}
+
def resolve_demands(self, ctx, arg):
log_util.setLoggerFilter(LOG, ctx.get('keyspace'), ctx.get('plan_id'))
diff --git a/conductor/conductor/opts.py b/conductor/conductor/opts.py
index b32a39b..e2ace38 100644
--- a/conductor/conductor/opts.py
+++ b/conductor/conductor/opts.py
@@ -24,10 +24,12 @@ import conductor.common.music.api
import conductor.common.music.messaging.component
import conductor.conf.inventory_provider
import conductor.conf.service_controller
+import conductor.conf.vim_controller
import conductor.controller.service
import conductor.controller.translator_svc
import conductor.data.plugins.inventory_provider.aai
import conductor.data.plugins.service_controller.sdnc
+import conductor.data.plugins.vim_controller.multicloud
import conductor.reservation.service
import conductor.service
import conductor.solver.service
@@ -53,6 +55,10 @@ def list_opts():
conductor.conf.inventory_provider.
INV_PROVIDER_EXT_MANAGER_OPTS)),
('aai', conductor.data.plugins.inventory_provider.aai.AAI_OPTS),
+ ('vim_controller', itertools.chain(
+ conductor.conf.vim_controller.VIM_CONTROLLER_EXT_MANAGER_OPTS)),
+ ('multicloud',
+ conductor.data.plugins.vim_controller.multicloud.MULTICLOUD_OPTS),
('service_controller', itertools.chain(
conductor.conf.service_controller.
SVC_CONTROLLER_EXT_MANAGER_OPTS)),
diff --git a/conductor/conductor/solver/optimizer/constraints/vim_fit.py b/conductor/conductor/solver/optimizer/constraints/vim_fit.py
new file mode 100644
index 0000000..6e6e052
--- /dev/null
+++ b/conductor/conductor/solver/optimizer/constraints/vim_fit.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+#
+# -------------------------------------------------------------------------
+# 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.
+#
+# -------------------------------------------------------------------------
+#
+
+'''Solver class for constraint type vim_fit
+ Multicloud capacity check'''
+
+# python imports
+
+from conductor.i18n import _LI
+# Conductor imports
+from conductor.solver.optimizer.constraints import constraint
+# Third-party library imports
+from oslo_log import log
+
+LOG = log.getLogger(__name__)
+
+
+class VimFit(constraint.Constraint):
+ def __init__(self, _name, _type, _demand_list, _priority=0,
+ _properties=None):
+ constraint.Constraint.__init__(
+ self, _name, _type, _demand_list, _priority)
+ self.properties = _properties
+
+ def solve(self, _decision_path, _candidate_list, _request):
+ '''
+ Solver for Multicloud vim_fit constraint type.
+ :param _decision_path: decision tree
+ :param _candidate_list: List of candidates
+ :param _request: solver request
+ :return: candidate_list with selected vim_list
+ '''
+ # call conductor engine with request parameters
+ cei = _request.cei
+ demand_name = _decision_path.current_demand.name
+ vim_request = self.properties.get('request')
+ LOG.info(_LI("Solving constraint type '{}' for demand - [{}]").format(
+ self.constraint_type, demand_name))
+ response = (
+ cei.get_candidates_with_vim_capacity(_candidate_list, vim_request))
+ if response:
+ _candidate_list = response
+ return _candidate_list
diff --git a/conductor/conductor/solver/request/parser.py b/conductor/conductor/solver/request/parser.py
index d7f3cec..0def215 100755
--- a/conductor/conductor/solver/request/parser.py
+++ b/conductor/conductor/solver/request/parser.py
@@ -34,6 +34,7 @@ from conductor.solver.optimizer.constraints \
import inventory_group
from conductor.solver.optimizer.constraints \
import service as service_constraint
+from conductor.solver.optimizer.constraints import vim_fit
from conductor.solver.optimizer.constraints import zone
from conductor.solver.request import demand
from conductor.solver.request import objective
@@ -215,6 +216,14 @@ class Parser(object):
constraint_demands,
_properties=c_property)
self.constraints[my_hpa_constraint.name] = my_hpa_constraint
+ elif constraint_type == "vim_fit":
+ LOG.debug("Creating constraint - {}".format(constraint_type))
+ c_property = constraint_info.get("properties")
+ my_vim_constraint = vim_fit.VimFit(constraint_id,
+ constraint_type,
+ constraint_demands,
+ _properties=c_property)
+ self.constraints[my_vim_constraint.name] = my_vim_constraint
else:
LOG.error("unknown constraint type {}".format(constraint_type))
return
@@ -326,12 +335,14 @@ class Parser(object):
constraint.rank = 4
elif constraint.constraint_type == "inventory_group":
constraint.rank = 5
- elif constraint.constraint_type == "instance_fit":
+ elif constraint.constraint_type == "vim_fit":
constraint.rank = 6
- elif constraint.constraint_type == "region_fit":
+ elif constraint.constraint_type == "instance_fit":
constraint.rank = 7
- else:
+ elif constraint.constraint_type == "region_fit":
constraint.rank = 8
+ else:
+ constraint.rank = 9
def attr_sort(self, attrs=['rank']):
# this helper for sorting the rank
diff --git a/conductor/conductor/solver/utils/constraint_engine_interface.py b/conductor/conductor/solver/utils/constraint_engine_interface.py
index 256e4bb..a662425 100644
--- a/conductor/conductor/solver/utils/constraint_engine_interface.py
+++ b/conductor/conductor/solver/utils/constraint_engine_interface.py
@@ -135,3 +135,20 @@ class ConstraintEngineInterface(object):
args=args)
LOG.debug("get_candidates_with_hpa response: {}".format(response))
return response
+
+ def get_candidates_with_vim_capacity(self, candidate_list, vim_request):
+ '''
+ Returns the candidate_list with required vim capacity.
+ :param candidate_list: list of candidates to process
+ :param requests: vim requests with required cpu, memory and disk
+ :return: candidate_list with required vim capacity.
+ '''
+ ctxt = {}
+ args = {"candidate_list": candidate_list,
+ "request": vim_request}
+ response = self.client.call(ctxt=ctxt,
+ method="get_candidates_with_vim_capacity",
+ args=args)
+ LOG.debug(
+ "get_candidates_with_vim_capacity response: {}".format(response))
+ return response
diff --git a/conductor/conductor/tests/unit/controller/test_translator.py b/conductor/conductor/tests/unit/controller/test_translator.py
index 26b1182..0e3bf8e 100644
--- a/conductor/conductor/tests/unit/controller/test_translator.py
+++ b/conductor/conductor/tests/unit/controller/test_translator.py
@@ -223,6 +223,68 @@ class TestNoExceptionTranslator(unittest.TestCase):
'type': 'distance_to_location'}}
self.assertEquals(self.Translator.parse_constraints(constraints), rtn)
+ def test_parse_vim_fit_constraint(self):
+ vim_fit_constraint = {
+ "check_cloud_capacity": {
+ "type": "vim_fit",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "controller": "multicloud",
+ "request": {
+ "vCPU": 10,
+ "Memory": {
+ "quantity": "10",
+ "unit": "GB"
+ },
+ "Storage": {
+ "quantity": "100",
+ "unit": "GB"
+ }
+ }
+ }
+ }
+ }
+ expected_response = {
+ "check_cloud_capacity_vG" : {
+ "type": "vim_fit",
+ "demands": "vG",
+ "name": "check_cloud_capacity",
+ "properties": {
+ "controller": "multicloud",
+ "request": {
+ "vCPU": 10,
+ "Memory": {
+ "quantity": "10",
+ "unit": "GB"
+ },
+ "Storage": {
+ "quantity": "100",
+ "unit": "GB"
+ }
+ }
+ }
+ }
+ }
+ vim_fit_constraint2 = {
+ "check_cloud_capacity": {
+ "type": "vim_fit",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "vim-controller": "multicloud"
+ }
+ }
+ }
+ self.maxDiff = None
+ self.assertEquals(expected_response, self.Translator.parse_constraints(
+ vim_fit_constraint))
+ self.assertRaises(TranslatorException,
+ self.Translator.parse_constraints,
+ vim_fit_constraint2)
+
@patch('conductor.controller.translator.Translator.create_components')
def test_parse_optimization(self, mock_create):
expected_parse = {'goal': 'min',
diff --git a/conductor/conductor/tests/unit/data/candidate_list.json b/conductor/conductor/tests/unit/data/candidate_list.json
index e29782c..789ab64 100644
--- a/conductor/conductor/tests/unit/data/candidate_list.json
+++ b/conductor/conductor/tests/unit/data/candidate_list.json
@@ -18,7 +18,8 @@
"complex_name": "dalls_one",
"cloud_owner": "att-aic",
"cloud_region_version": "1.1",
- "physical_location_id": "DLLSTX55"
+ "physical_location_id": "DLLSTX55",
+ "vim-id": "att-aic_DLLSTX55"
},
{
"candidate_id": "NYCNY55",
@@ -37,7 +38,8 @@
"complex_name": "ny_one",
"cloud_owner": "att-aic",
"cloud_region_version": "1.1",
- "physical_location_id": "NYCNY55"
+ "physical_location_id": "NYCNY55",
+ "vim-id": "att-aic_DLLSTX55"
}
]
} \ No newline at end of file
diff --git a/conductor/conductor/tests/unit/data/hpa_constraints.json b/conductor/conductor/tests/unit/data/hpa_constraints.json
index 3954cd5..9139204 100644
--- a/conductor/conductor/tests/unit/data/hpa_constraints.json
+++ b/conductor/conductor/tests/unit/data/hpa_constraints.json
@@ -165,6 +165,26 @@
"location": "customer_loc"
}
}
+ },
+ {
+ "check_cloud_capacity": {
+ "type": "vim_fit",
+ "demands": ["vG"],
+ "properties": {
+ "controller": "multicloud",
+ "request": {
+ "vCPU": 10,
+ "Memory": {
+ "quantity": "10",
+ "unit": "GB"
+ },
+ "Storage": {
+ "quantity": "100",
+ "unit": "GB"
+ }
+ }
+ }
+ }
}
]
}
diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_multicloud.py b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_multicloud.py
new file mode 100644
index 0000000..aacaab4
--- /dev/null
+++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_multicloud.py
@@ -0,0 +1,96 @@
+#
+# -------------------------------------------------------------------------
+# 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 unittest
+
+import conductor.data.plugins.vim_controller.multicloud as mc
+import mock
+from oslo_config import cfg
+
+
+class TestMultiCloud(unittest.TestCase):
+
+ def setUp(self):
+ cli_opts = [
+ cfg.BoolOpt('debug',
+ short='d',
+ default=False,
+ help='Print debugging output.'),
+ ]
+ cfg.CONF.register_cli_opts(cli_opts)
+ self.mc_ep = mc.MULTICLOUD()
+ self.mc_ep.conf.set_override('debug', False)
+
+ def tearDown(self):
+ mock.patch.stopall()
+
+ def test_initialize(self):
+ self.mc_ep.initialize()
+ self.assertEqual('http://msb.onap.org/api/multicloud',
+ self.mc_ep.rest.server_url)
+ self.assertEqual((float(3.05), float(30)), self.mc_ep.rest.timeout)
+ self.assertEqual(None, super(mc.MULTICLOUD, self.mc_ep).name())
+ self.assertEqual("MultiCloud", self.mc_ep.name())
+
+ @mock.patch.object(mc.LOG, 'error')
+ @mock.patch.object(mc.LOG, 'debug')
+ @mock.patch.object(mc.LOG, 'info')
+ @mock.patch('conductor.common.rest.REST.request')
+ def test_check_vim_capacity(self, rest_mock, i_mock, d_mock, e_mock):
+ self.mc_ep.initialize()
+ response = mock.MagicMock()
+ response.status_code = 400
+ response.text = {"VIMs": ["att-aic_NYCNY33"]}
+ vim_request = {
+ "vCPU": 10,
+ "Memory": {
+ "quantity": "10",
+ "unit": "GB"
+ },
+ "Storage": {
+ "quantity": "100",
+ "unit": "GB"
+ },
+ "VIMs": ["att-aic_NYCNY33"]
+ }
+
+ rest_mock.return_value = None
+ self.assertEqual(None, self.mc_ep.check_vim_capacity(vim_request))
+ rest_mock.return_value = response
+ self.assertEqual(None, self.mc_ep.check_vim_capacity(vim_request))
+ response.status_code = 200
+ response.json.return_value = response.text
+ rest_mock.return_value = response
+ self.assertEqual(['att-aic_NYCNY33'],
+ self.mc_ep.check_vim_capacity(vim_request))
+ response.json.return_value = None
+ rest_mock.return_value = response
+ self.assertEqual(None, self.mc_ep.check_vim_capacity(vim_request))
+ response.text = {"VIMs": []}
+ response.json.return_value = response.text
+ rest_mock.return_value = response
+ self.assertEqual(None, self.mc_ep.check_vim_capacity(vim_request))
+ response.text = {"VIMs": None}
+ response.json.return_value = response.text
+ rest_mock.return_value = response
+ self.assertEqual(None, self.mc_ep.check_vim_capacity(vim_request))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/conductor/conductor/tests/unit/data/test_service.py b/conductor/conductor/tests/unit/data/test_service.py
index 385b45d..3f2adde 100644
--- a/conductor/conductor/tests/unit/data/test_service.py
+++ b/conductor/conductor/tests/unit/data/test_service.py
@@ -28,6 +28,7 @@ import yaml
from conductor.common.utils import conductor_logging_util as log_util
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.plugins.vim_controller import extensions as vc_ext
from conductor.data.service import DataEndpoint
from oslo_config import cfg
@@ -37,9 +38,13 @@ class TestDataEndpoint(unittest.TestCase):
def setUp(self):
ip_ext_manager = (
ip_ext.Manager(cfg.CONF, 'conductor.inventory_provider.plugin'))
+ vc_ext_manager = (
+ vc_ext.Manager(cfg.CONF, 'conductor.vim_controller.plugin'))
sc_ext_manager = (
sc_ext.Manager(cfg.CONF, 'conductor.service_controller.plugin'))
- self.data_ep = DataEndpoint(ip_ext_manager, sc_ext_manager)
+ self.data_ep = DataEndpoint(ip_ext_manager,
+ vc_ext_manager,
+ sc_ext_manager)
def tearDown(self):
pass
@@ -274,6 +279,41 @@ class TestDataEndpoint(unittest.TestCase):
self.assertEqual(expected_response,
self.data_ep.get_candidates_with_hpa(None, args))
+ @mock.patch.object(service.LOG, 'warn')
+ @mock.patch.object(service.LOG, 'info')
+ @mock.patch.object(stevedore.ExtensionManager, 'names')
+ @mock.patch.object(stevedore.ExtensionManager, 'map_method')
+ def test_get_candidates_with_vim_capacity(self, vim_mock, ext_mock1,
+ info_mock, warn_mock):
+ req_json_file = './conductor/tests/unit/data/candidate_list.json'
+ hpa_json_file = './conductor/tests/unit/data/hpa_constraints.json'
+ hpa_json = yaml.safe_load(open(hpa_json_file).read())
+ req_json = yaml.safe_load(open(req_json_file).read())
+ candidate_list = req_json['candidate_list']
+ ext_mock1.return_value = ['MultiCloud']
+ (constraint_id, constraint_info) = \
+ hpa_json["conductor_solver"]["constraints"][2].items()[0]
+ vim_request = constraint_info['properties']['request']
+ ctxt = {}
+ args = {"candidate_list": candidate_list,
+ "request": vim_request}
+ vim_mock.return_value = ['att-aic_DLLSTX55']
+ self.assertEqual({'response': candidate_list, 'error': False},
+ self.data_ep.get_candidates_with_vim_capacity(ctxt,
+ args))
+ vim_mock.return_value = ['att-aic_NYCNY33']
+ self.assertEqual({'response': [candidate_list[0]], 'error': False},
+ self.data_ep.get_candidates_with_vim_capacity(ctxt,
+ args))
+ vim_mock.return_value = []
+ self.assertEqual({'response': candidate_list, 'error': True},
+ self.data_ep.get_candidates_with_vim_capacity(ctxt,
+ args))
+ vim_mock.return_value = None
+ self.assertEqual({'response': candidate_list, 'error': True},
+ self.data_ep.get_candidates_with_vim_capacity(ctxt,
+ args))
+
def generate_args(candidate_list, features, label_name):
arg_candidate_list = copy.deepcopy(candidate_list)
diff --git a/conductor/conductor/tests/unit/solver/candidate_list.json b/conductor/conductor/tests/unit/solver/candidate_list.json
index e29782c..789ab64 100644
--- a/conductor/conductor/tests/unit/solver/candidate_list.json
+++ b/conductor/conductor/tests/unit/solver/candidate_list.json
@@ -18,7 +18,8 @@
"complex_name": "dalls_one",
"cloud_owner": "att-aic",
"cloud_region_version": "1.1",
- "physical_location_id": "DLLSTX55"
+ "physical_location_id": "DLLSTX55",
+ "vim-id": "att-aic_DLLSTX55"
},
{
"candidate_id": "NYCNY55",
@@ -37,7 +38,8 @@
"complex_name": "ny_one",
"cloud_owner": "att-aic",
"cloud_region_version": "1.1",
- "physical_location_id": "NYCNY55"
+ "physical_location_id": "NYCNY55",
+ "vim-id": "att-aic_DLLSTX55"
}
]
} \ No newline at end of file
diff --git a/conductor/conductor/tests/unit/solver/hpa_constraints.json b/conductor/conductor/tests/unit/solver/hpa_constraints.json
index 3954cd5..9139204 100644
--- a/conductor/conductor/tests/unit/solver/hpa_constraints.json
+++ b/conductor/conductor/tests/unit/solver/hpa_constraints.json
@@ -165,6 +165,26 @@
"location": "customer_loc"
}
}
+ },
+ {
+ "check_cloud_capacity": {
+ "type": "vim_fit",
+ "demands": ["vG"],
+ "properties": {
+ "controller": "multicloud",
+ "request": {
+ "vCPU": 10,
+ "Memory": {
+ "quantity": "10",
+ "unit": "GB"
+ },
+ "Storage": {
+ "quantity": "100",
+ "unit": "GB"
+ }
+ }
+ }
+ }
}
]
}
diff --git a/conductor/conductor/tests/unit/solver/test_vim_fit.py b/conductor/conductor/tests/unit/solver/test_vim_fit.py
new file mode 100644
index 0000000..9bbea2b
--- /dev/null
+++ b/conductor/conductor/tests/unit/solver/test_vim_fit.py
@@ -0,0 +1,82 @@
+#
+# -------------------------------------------------------------------------
+# 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 unittest
+
+import mock
+import yaml
+from conductor.solver.optimizer.constraints import vim_fit
+from conductor.solver.utils import constraint_engine_interface as cei
+
+
+class TestVimFit(unittest.TestCase):
+
+ def setUp(self):
+ req_json_file = './conductor/tests/unit/solver/candidate_list.json'
+ hpa_json_file = './conductor/tests/unit/solver/hpa_constraints.json'
+ hpa_json = yaml.safe_load(open(hpa_json_file).read())
+ req_json = yaml.safe_load(open(req_json_file).read())
+
+ (constraint_id, constraint_info) = \
+ hpa_json["conductor_solver"]["constraints"][2].items()[0]
+ c_property = constraint_info['properties']
+ constraint_type = constraint_info['properties']
+ constraint_demands = list()
+ parsed_demands = constraint_info['demands']
+ if isinstance(parsed_demands, list):
+ for d in parsed_demands:
+ constraint_demands.append(d)
+ self.vim_fit = vim_fit.VimFit(constraint_id,
+ constraint_type,
+ constraint_demands,
+ _properties=c_property)
+
+ self.candidate_list = req_json['candidate_list']
+
+ def tearDown(self):
+ pass
+
+ @mock.patch.object(vim_fit.LOG, 'error')
+ @mock.patch.object(vim_fit.LOG, 'info')
+ @mock.patch.object(vim_fit.LOG, 'debug')
+ def test_solve(self, debug_mock, info_mock, error_mock):
+
+ self.maxDiff = None
+
+ mock_decision_path = mock.MagicMock()
+ mock_decision_path.current_demand.name = 'vG'
+ request_mock = mock.MagicMock()
+ client_mock = mock.MagicMock()
+ client_mock.call.return_value = None
+ request_mock.cei = cei.ConstraintEngineInterface(client_mock)
+
+ self.assertEqual(self.candidate_list,
+ self.vim_fit.solve(mock_decision_path,
+ self.candidate_list, request_mock))
+ client_mock.call.return_value = self.candidate_list[1]
+ request_mock.cei = cei.ConstraintEngineInterface(client_mock)
+
+ self.assertEqual(self.candidate_list[1],
+ self.vim_fit.solve(mock_decision_path,
+ self.candidate_list, request_mock))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/conductor/setup.cfg b/conductor/setup.cfg
index 8e3fc56..c6fd3c4 100644
--- a/conductor/setup.cfg
+++ b/conductor/setup.cfg
@@ -48,6 +48,9 @@ console_scripts =
conductor.inventory_provider.plugin =
aai = conductor.data.plugins.inventory_provider.aai:AAI
+conductor.vim_controller.plugin =
+ multicloud = conductor.data.plugins.vim_controller.multicloud:MULTICLOUD
+
conductor.service_controller.plugin =
sdnc = conductor.data.plugins.service_controller.sdnc:SDNC