diff options
author | Ying Ruoyu <ruoyu.ying@intel.com> | 2018-03-15 06:33:03 -0400 |
---|---|---|
committer | Dileep Ranganathan <dileep.ranganathan@intel.com> | 2018-03-28 05:53:16 -0700 |
commit | 04bf647bc948fed77d2574e0f97ffbab5f857522 (patch) | |
tree | 0004ca038db1fa856cef4ec597bdaac2b9a14fa7 | |
parent | 39f63ef31ba3f0dc0db3ee8d5ff502a715702e1a (diff) |
HPA Enablement for controller Translator Module
Add support for HPA constraints translation
Add unit tests for HPA constraints validation
Updated with mandatory and score parameters
Updated flavorLabel and flavorProperties instead of label and features
Change-Id: Iec8fff2ad71f8ebeb58ef807d8d2b95ed3635ec1
Issue-ID: OPTFRA-168
Signed-off-by: Ying Ruoyu <ruoyu.ying@intel.com>
-rw-r--r-- | conductor/conductor/controller/translator.py | 59 | ||||
-rw-r--r-- | conductor/conductor/tests/unit/controller/test_translator.py | 257 |
2 files changed, 311 insertions, 5 deletions
diff --git a/conductor/conductor/controller/translator.py b/conductor/conductor/controller/translator.py index 724b068..7bc212b 100644 --- a/conductor/conductor/controller/translator.py +++ b/conductor/conductor/controller/translator.py @@ -99,7 +99,16 @@ CONSTRAINTS = { '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', + 'unit'] class TranslatorException(Exception): @@ -512,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( @@ -541,6 +550,51 @@ 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") + for name in attr.keys(): + if name not in HPA_ATTRIBUTES: + raise TranslatorException( + "Invalid attribute '{}' found inside HPA " + "feature attributes".format(name)) + if list(attr.keys()) < ['hpa-attribute-key', + 'hpa-attribute-value', 'operator']: + raise TranslatorException( + "Lack of compulsory elements " + "inside HPA feature attributes") + def parse_constraints(self, constraints): """Validate/prepare constraints for use by the solver.""" if not isinstance(constraints, dict): @@ -589,6 +643,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/tests/unit/controller/test_translator.py b/conductor/conductor/tests/unit/controller/test_translator.py index 0e3bf8e..3dedfdf 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,255 @@ 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": { |