diff options
Diffstat (limited to 'conductor')
-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": { |