summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--conductor/conductor/conf/vim_controller.py33
-rw-r--r--conductor/conductor/controller/translator.py81
-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.py57
-rw-r--r--conductor/conductor/opts.py6
-rw-r--r--conductor/conductor/solver/optimizer/constraints/hpa.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.py24
-rw-r--r--conductor/conductor/tests/unit/controller/test_translator.py320
-rw-r--r--conductor/conductor/tests/unit/data/candidate_list.json6
-rw-r--r--conductor/conductor/tests/unit/data/hpa_constraints.json28
-rw-r--r--conductor/conductor/tests/unit/data/plugins/inventory_provider/test_multicloud.py96
-rw-r--r--conductor/conductor/tests/unit/data/test_service.py54
-rw-r--r--conductor/conductor/tests/unit/solver/candidate_list.json6
-rw-r--r--conductor/conductor/tests/unit/solver/hpa_constraints.json28
-rw-r--r--conductor/conductor/tests/unit/solver/test_vim_fit.py82
-rw-r--r--conductor/setup.cfg3
21 files changed, 1091 insertions, 42 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..2913c69 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,7 +94,21 @@ CONSTRAINTS = {
'category': ['disaster', 'region', 'complex', 'country',
'time', 'maintenance']},
},
+ 'vim_fit': {
+ 'split': True,
+ 'required': ['controller'],
+ 'optional': ['request'],
+ },
+ 'hpa': {
+ 'split': True,
+ 'required': ['evaluate'],
+ },
}
+HPA_FEATURES = ['architecture', 'hpa-feature', 'hpa-feature-attributes',
+ 'hpa-version', 'mandatory']
+HPA_OPTIONAL = ['score']
+HPA_ATTRIBUTES = ['hpa-attribute-key', 'hpa-attribute-value', 'operator']
+HPA_ATTRIBUTES_OPTIONAL = ['unit']
class TranslatorException(Exception):
@@ -508,7 +521,7 @@ class Translator(object):
resolved_demands = \
response and response.get('resolved_demands')
- required_candidates = resolved_demands\
+ required_candidates = resolved_demands \
.get('required_candidates')
if not resolved_demands:
raise TranslatorException(
@@ -537,6 +550,57 @@ class Translator(object):
return parsed
+ def validate_hpa_constraints(self, req_prop, value):
+ for para in value.get(req_prop):
+ # Make sure there is at least one
+ # set of flavorLabel and flavorProperties
+ if not para.get('flavorLabel') \
+ or not para.get('flavorProperties') \
+ or para.get('flavorLabel') == '' \
+ or para.get('flavorProperties') == '':
+ raise TranslatorException(
+ "HPA requirements need at least "
+ "one set of flavorLabel and flavorProperties"
+ )
+ for feature in para.get('flavorProperties'):
+ if type(feature) is not dict:
+ raise TranslatorException("HPA feature must be a dict")
+ # process mandatory parameter
+ hpa_mandatory = set(HPA_FEATURES).difference(feature.keys())
+ if bool(hpa_mandatory):
+ raise TranslatorException(
+ "Lack of compulsory elements inside HPA feature")
+ # process optional parameter
+ hpa_optional = set(feature.keys()).difference(HPA_FEATURES)
+ if hpa_optional and not hpa_optional.issubset(HPA_OPTIONAL):
+ raise TranslatorException(
+ "Lack of compulsory elements inside HPA feature")
+ if feature.get('mandatory') == 'False' and not feature.get(
+ 'score'):
+ raise TranslatorException(
+ "Score needs to be present if mandatory is False")
+
+ for attr in feature.get('hpa-feature-attributes'):
+ if type(attr) is not dict:
+ raise TranslatorException(
+ "HPA feature attributes must be a dict")
+
+ # process mandatory hpa attribute parameter
+ hpa_attr_mandatory = set(HPA_ATTRIBUTES).difference(
+ attr.keys())
+ if bool(hpa_attr_mandatory):
+ raise TranslatorException(
+ "Lack of compulsory elements inside HPA "
+ "feature atrributes")
+ # process optional hpa attribute parameter
+ hpa_attr_optional = set(attr.keys()).difference(
+ HPA_ATTRIBUTES)
+ if hpa_attr_optional and not hpa_attr_optional.issubset(
+ HPA_ATTRIBUTES_OPTIONAL):
+ raise TranslatorException(
+ "Invalid attributes '{}' found inside HPA "
+ "feature attributes".format(hpa_attr_optional))
+
def parse_constraints(self, constraints):
"""Validate/prepare constraints for use by the solver."""
if not isinstance(constraints, dict):
@@ -585,6 +649,9 @@ class Translator(object):
"No value specified for property '{}' in "
"constraint named '{}'".format(
req_prop, name))
+ # For HPA constraints
+ if constraint_type == 'hpa':
+ self.validate_hpa_constraints(req_prop, value)
# Make sure there are no unknown properties
optional = constraint_def.get('optional', [])
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..5912963 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 = {}
@@ -437,7 +443,7 @@ class DataEndpoint(object):
error = False
candidate_list = arg["candidate_list"]
label_name = arg["label_name"]
- features = arg["features"]
+ flavorProperties = arg["flavorProperties"]
discard_set = set()
for candidate in candidate_list:
# perform this check only for cloud candidates
@@ -457,7 +463,7 @@ class DataEndpoint(object):
results = self.ip_ext_manager.map_method(
'match_hpa',
candidate=candidate,
- features=features
+ features=flavorProperties
)
if results and len(results) > 0:
@@ -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/hpa.py b/conductor/conductor/solver/optimizer/constraints/hpa.py
index a7e8d3c..9ef37df 100644
--- a/conductor/conductor/solver/optimizer/constraints/hpa.py
+++ b/conductor/conductor/solver/optimizer/constraints/hpa.py
@@ -54,11 +54,11 @@ class HPA(constraint.Constraint):
self.constraint_type, demand_name))
vm_label_list = self.properties.get('evaluate')
for vm_demand in vm_label_list:
- label_name = vm_demand['label']
- features = vm_demand['features']
+ label_name = vm_demand['flavorLabel']
+ flavorProperties = vm_demand['flavorProperties']
response = (cei.get_candidates_with_hpa(label_name,
_candidate_list,
- features))
+ flavorProperties))
if response:
_candidate_list = response
else:
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..43331aa 100644
--- a/conductor/conductor/solver/utils/constraint_engine_interface.py
+++ b/conductor/conductor/solver/utils/constraint_engine_interface.py
@@ -117,21 +117,39 @@ class ConstraintEngineInterface(object):
# response is a list of (candidate, cost) tuples
return response
- def get_candidates_with_hpa(self, label_name, candidate_list, features):
+ def get_candidates_with_hpa(self, label_name, candidate_list,
+ flavorProperties):
'''
Returns the candidate_list with an addition of flavor_mapping for
matching cloud candidates with hpa constraints.
:param label_name: vm_label_name passed from the SO/Policy
:param candidate_list: list of candidates to process
- :param features: hpa features for this vm_label_name
+ :param flavorProperties: hpa features for this vm_label_name
:return: candidate_list with hpa features and flavor mapping
'''
ctxt = {}
args = {"candidate_list": candidate_list,
- "features": features,
+ "flavorProperties": flavorProperties,
"label_name": label_name}
response = self.client.call(ctxt=ctxt,
method="get_candidates_with_hpa",
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..ba0e3ec 100644
--- a/conductor/conductor/tests/unit/controller/test_translator.py
+++ b/conductor/conductor/tests/unit/controller/test_translator.py
@@ -19,15 +19,15 @@
"""Test classes for translator"""
import os
-import yaml
-import uuid
import unittest
+import uuid
+import yaml
+from conductor import __file__ as conductor_root
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
+from oslo_config import cfg
def get_template():
@@ -223,6 +223,318 @@ class TestNoExceptionTranslator(unittest.TestCase):
'type': 'distance_to_location'}}
self.assertEquals(self.Translator.parse_constraints(constraints), rtn)
+ def test_parse_hpa_constraints(self):
+ hpa_constraint = {
+ "hpa_constraint": {
+ "type": "hpa",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "evaluate": [
+ {'flavorLabel': 'xx',
+ 'flavorProperties': [{
+ 'hpa-feature': 'BasicCapabilities',
+ 'hpa-version': 'v1',
+ 'architecture': 'generic',
+ 'mandatory': 'False',
+ 'score': '5',
+ 'hpa-feature-attributes': [
+ {
+ 'hpa-attribute-key': 'numVirtualCpu',
+ 'hpa-attribute-value': '4',
+ 'operator': '='
+ },
+ {
+ 'hpa-attribute-key': 'virtualMemSize',
+ 'hpa-attribute-value': '4',
+ 'operator': '=',
+ 'unit': 'GB'
+ }
+ ]
+ }], }
+ ]
+ }}}
+ rtn = {
+ 'hpa_constraint_vG': {
+ 'demands': 'vG',
+ 'name': 'hpa_constraint',
+ 'properties': {'evaluate': [{
+ 'flavorProperties': [
+ {'architecture': 'generic',
+ 'mandatory': 'False',
+ 'score': '5',
+ 'hpa-feature': 'BasicCapabilities',
+ 'hpa-feature-attributes': [
+ {
+ 'hpa-attribute-key': 'numVirtualCpu',
+ 'hpa-attribute-value': '4',
+ 'operator': '='
+ },
+ {
+ 'hpa-attribute-key': 'virtualMemSize',
+ 'hpa-attribute-value': '4',
+ 'operator': '=',
+ 'unit': 'GB'
+ }
+ ],
+ 'hpa-version': 'v1'}],
+ 'flavorLabel': 'xx'}]},
+ 'type': 'hpa'
+ }
+ }
+
+ self.assertEquals(self.Translator.parse_constraints(hpa_constraint),
+ rtn)
+
+ hpa_constraint_2 = {
+ "hpa_constraint": {
+ "type": "hpa",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "evaluate": [
+ {'flavorLabel': 'xx',
+ 'flavorProperties': [{
+ 'hpa-feature': 'BasicCapabilities',
+ 'hpa-version': 'v1',
+ 'architecture': 'generic',
+ 'mandatory': 'True',
+ 'hpa-feature-attributes': [
+ {
+ 'hpa-attribute-key': 'numVirtualCpu',
+ 'hpa-attribute-value': '4',
+ 'operator': '='
+ },
+ {
+ 'hpa-attribute-key': 'virtualMemSize',
+ 'hpa-attribute-value': '4',
+ 'operator': '=',
+ 'unit': 'GB'
+ }
+ ]
+ }], }
+ ]
+ }}}
+ rtn_2 = {
+ 'hpa_constraint_vG': {
+ 'demands': 'vG',
+ 'name': 'hpa_constraint',
+ 'properties': {'evaluate': [{
+ 'flavorProperties': [
+ {'architecture': 'generic',
+ 'mandatory': 'True',
+ 'hpa-feature': 'BasicCapabilities',
+ 'hpa-feature-attributes': [
+ {
+ 'hpa-attribute-key': 'numVirtualCpu',
+ 'hpa-attribute-value': '4',
+ 'operator': '='
+ },
+ {
+ 'hpa-attribute-key': 'virtualMemSize',
+ 'hpa-attribute-value': '4',
+ 'operator': '=',
+ 'unit': 'GB'
+ }
+ ],
+ 'hpa-version': 'v1'}],
+ 'flavorLabel': 'xx'}]},
+ 'type': 'hpa'
+ }
+ }
+
+ self.assertEquals(self.Translator.parse_constraints(hpa_constraint_2),
+ rtn_2)
+
+ def test_parse_hpa_constraints_format_validation(self):
+ hpa_constraint_1 = {
+ "hpa_constraint": {
+ "type": "hpa",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "evaluate": [{'flavor': 'xx',
+ 'flavorProperties': []}]
+ }
+ }
+ }
+ hpa_constraint_2 = {
+ "hpa_constraint": {
+ "type": "hpa",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "evaluate": [
+ {'flavorLabel': 'xx',
+ 'flavorProperties': [
+ {
+ 'hpa-feature': '',
+ 'hpa-version': '',
+ 'architecture': '',
+ 'mandatory': '',
+ 'hpa-feature-attributes': [''],
+ }
+ ]}
+ ]
+ }
+ }
+ }
+
+ hpa_constraint_3 = {
+ "hpa_constraint": {
+ "type": "hpa",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "evaluate": [
+ {
+ "flavorLabel": "xx",
+ "flavorProperties": [
+ {
+ "hpa-feature": "BasicCapabilities",
+ "hpa-version": "v1",
+ "architecture": "generic",
+ "mandatory": "False",
+ "score": "5",
+ "hpa-feature-attributes": [
+ {
+ "hpa-attribute-key": "numVirtualCpu",
+ "hpa-attribute-value": "4"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+
+ hpa_constraint_4 = {
+ "hpa_constraint": {
+ "type": "hpa",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "evaluate": [{'flavorLabel': 'xx',
+ 'flavorProperties': [{
+ 'hpa-feature': '',
+ 'architecture': '',
+ 'mandatory': '',
+ 'hpa-feature-attributes': [''],
+ }]}]
+ }
+ }
+ }
+
+ hpa_constraint_5 = {
+ "hpa_constraint": {
+ "type": "hpa",
+ "demands": [
+ "vG"
+ ],
+ "properties": {
+ "evaluate": [
+ {'flavorLabel': 'xx',
+ 'flavorProperties': [
+ {
+ 'hpa-feature': 'BasicCapabilities',
+ 'hpa-version': 'v1',
+ 'architecture': 'generic',
+ 'mandatory': 'False',
+ 'hpa-feature-attributes': [
+ {
+ 'hpa-attribute-key': 'numVirtualCpu',
+ 'hpa-attribute-value': '4',
+
+ },
+ ]
+ }
+ ], }
+ ]
+ }
+ }
+ }
+
+ self.assertRaises(TranslatorException,
+ self.Translator.parse_constraints, hpa_constraint_1)
+ self.assertRaises(TranslatorException,
+ self.Translator.parse_constraints, hpa_constraint_2)
+ self.assertRaises(TranslatorException,
+ self.Translator.parse_constraints, hpa_constraint_3)
+ self.assertRaises(TranslatorException,
+ self.Translator.parse_constraints, hpa_constraint_4)
+ self.assertRaises(TranslatorException,
+ self.Translator.parse_constraints, hpa_constraint_5)
+
+ 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..829eaf3 100644
--- a/conductor/conductor/tests/unit/data/hpa_constraints.json
+++ b/conductor/conductor/tests/unit/data/hpa_constraints.json
@@ -9,7 +9,7 @@
"properties": {
"evaluate": [
{
- "features": [
+ "flavorProperties": [
{
"architecture": "generic",
"hpa-feature": "basicCapabilities",
@@ -80,10 +80,10 @@
"hpa-version": "v1"
}
],
- "label": "flavor_label_1"
+ "flavorLabel": "flavor_label_1"
},
{
- "features": [
+ "flavorProperties": [
{
"architecture": "generic",
"hpa-feature": "basicCapabilities",
@@ -150,7 +150,7 @@
"hpa-version": "v1"
}
],
- "label": "flavor_label_2"
+ "flavorLabel": "flavor_label_2"
}
]
}
@@ -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..4b841de 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
@@ -232,14 +237,14 @@ class TestDataEndpoint(unittest.TestCase):
(constraint_id, constraint_info) = \
hpa_json["conductor_solver"]["constraints"][0].items()[0]
hpa_constraint = constraint_info['properties']
- features = hpa_constraint['evaluate'][0]['features']
- label_name = hpa_constraint['evaluate'][0]['label']
+ flavorProperties = hpa_constraint['evaluate'][0]['flavorProperties']
+ label_name = hpa_constraint['evaluate'][0]['flavorLabel']
ext_mock1.return_value = ['aai']
flavor_info = {"flavor-id": "vim-flavor-id1",
"flavor-name": "vim-flavor-name1"}
hpa_mock.return_value = [flavor_info]
self.maxDiff = None
- args = generate_args(candidate_list, features, label_name)
+ args = generate_args(candidate_list, flavorProperties, label_name)
hpa_candidate_list = copy.deepcopy(candidate_list)
hpa_candidate_list[1]['flavor_map'] = {}
hpa_candidate_list[1]['flavor_map'][label_name] = "vim-flavor-name1"
@@ -249,7 +254,7 @@ class TestDataEndpoint(unittest.TestCase):
hpa_candidate_list2 = list()
hpa_candidate_list2.append(copy.deepcopy(candidate_list[0]))
- args = generate_args(candidate_list, features, label_name)
+ args = generate_args(candidate_list, flavorProperties, label_name)
hpa_mock.return_value = []
expected_response = {'response': hpa_candidate_list2, 'error': False}
self.assertEqual(expected_response,
@@ -274,11 +279,46 @@ 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):
+def generate_args(candidate_list, flavorProperties, label_name):
arg_candidate_list = copy.deepcopy(candidate_list)
args = {"candidate_list": arg_candidate_list,
- "features": features,
+ "flavorProperties": flavorProperties,
"label_name": label_name}
return args
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..829eaf3 100644
--- a/conductor/conductor/tests/unit/solver/hpa_constraints.json
+++ b/conductor/conductor/tests/unit/solver/hpa_constraints.json
@@ -9,7 +9,7 @@
"properties": {
"evaluate": [
{
- "features": [
+ "flavorProperties": [
{
"architecture": "generic",
"hpa-feature": "basicCapabilities",
@@ -80,10 +80,10 @@
"hpa-version": "v1"
}
],
- "label": "flavor_label_1"
+ "flavorLabel": "flavor_label_1"
},
{
- "features": [
+ "flavorProperties": [
{
"architecture": "generic",
"hpa-feature": "basicCapabilities",
@@ -150,7 +150,7 @@
"hpa-version": "v1"
}
],
- "label": "flavor_label_2"
+ "flavorLabel": "flavor_label_2"
}
]
}
@@ -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