HPA Enablement for controller Translator Module 73/35873/9
authorYing Ruoyu <ruoyu.ying@intel.com>
Thu, 15 Mar 2018 10:33:03 +0000 (06:33 -0400)
committerDileep Ranganathan <dileep.ranganathan@intel.com>
Wed, 28 Mar 2018 12:53:16 +0000 (05:53 -0700)
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>
conductor/conductor/controller/translator.py
conductor/conductor/tests/unit/controller/test_translator.py

index 724b068..7bc212b 100644 (file)
@@ -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', [])
index 0e3bf8e..3dedfdf 100644 (file)
 """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": {