summaryrefslogtreecommitdiffstats
path: root/conductor
diff options
context:
space:
mode:
Diffstat (limited to 'conductor')
-rw-r--r--conductor/conductor/controller/translator.py59
-rw-r--r--conductor/conductor/tests/unit/controller/test_translator.py257
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": {