Merge "Implement LoadBalancer node type per TOSCA v1.0 spec"

This commit is contained in:
Jenkins
2016-02-16 02:15:04 +00:00
committed by Gerrit Code Review
8 changed files with 191 additions and 56 deletions

View File

@@ -49,6 +49,18 @@ class TOSCAException(Exception):
def __str__(self):
return self.message
@staticmethod
def generate_inv_schema_property_error(self, attr, value, valid_values):
msg = (_('Schema definition of "%(propname)s" has '
'"%(attr)s" attribute with invalid value '
'"%(value1)s". The value must be one of '
'"%(value2)s".') % {"propname": self.name,
"attr": attr,
"value1": value,
"value2": valid_values})
ExceptionCollector.appendException(
InvalidSchemaError(message=msg))
@staticmethod
def set_fatal_format_exception(flag):
if isinstance(flag, bool):

View File

@@ -366,6 +366,25 @@ tosca.nodes.ObjectStorage:
storage_endpoint:
type: tosca.capabilities.Endpoint
tosca.nodes.LoadBalancer:
derived_from: tosca.nodes.Root
properties:
algorithm:
type: string
required: false
status: experimental
capabilities:
client:
type: tosca.capabilities.Endpoint.Public
occurrences: [0, UNBOUNDED]
description: the Floating (IP) clients on the public network can connect to
requirements:
- application:
capability: tosca.capabilities.Endpoint
relationship: tosca.relationships.RoutesTo
occurrences: [0, UNBOUNDED]
description: Connection to one or more load balanced applications
##########################################################################
# Relationship Type.
# A Relationship Type is a reusable entity that defines the type of one

View File

@@ -12,19 +12,35 @@
from toscaparser.common.exception import ExceptionCollector
from toscaparser.common.exception import InvalidSchemaError
from toscaparser.common.exception import TOSCAException
from toscaparser.utils.gettextutils import _
class PropertyDef(object):
'''TOSCA built-in Property type.'''
VALID_PROPERTY_KEYNAMES = (PROPERTY_KEYNAME_DEFAULT,
PROPERTY_KEYNAME_REQUIRED,
PROPERTY_KEYNAME_STATUS) = \
('default', 'required', 'status')
PROPERTY_REQUIRED_DEFAULT = False
VALID_REQUIRED_VALUES = ['true', 'false']
VALID_STATUS_VALUES = (PROPERTY_STATUS_SUPPORTED,
PROPERTY_STATUS_EXPERIMENTAL) = \
('supported', 'experimental')
PROPERTY_STATUS_DEFAULT = PROPERTY_STATUS_SUPPORTED
def __init__(self, name, value=None, schema=None):
self.name = name
self.value = value
self.schema = schema
self._status = self.PROPERTY_STATUS_DEFAULT
self._required = self.PROPERTY_REQUIRED_DEFAULT
# Validate required 'type' property exists
try:
self.schema['type']
except KeyError:
@@ -33,32 +49,52 @@ class PropertyDef(object):
ExceptionCollector.appendException(
InvalidSchemaError(message=msg))
if 'required' in self.schema:
required = self.schema['required']
if not isinstance(required, bool):
if required.lower() not in self.VALID_REQUIRED_VALUES:
valid_values = ', '.join(self.VALID_REQUIRED_VALUES)
msg = (_('Schema definition of "%(propname)s" has '
'"required" attribute with invalid value '
'"%(value1)s". The value must be one of '
'"%(value2)s".') % {"propname": self.name,
"value1": required,
"value2": valid_values})
ExceptionCollector.appendException(
InvalidSchemaError(message=msg))
@property
def required(self):
if self.schema:
for prop_key, prop_value in self.schema.items():
if prop_key == 'required' and prop_value:
return True
return False
self._load_required_attr_from_schema()
self._load_status_attr_from_schema()
@property
def default(self):
if self.schema:
for prop_key, prop_value in self.schema.items():
if prop_key == 'default':
if prop_key == self.PROPERTY_KEYNAME_DEFAULT:
return prop_value
return None
@property
def required(self):
return self._required
def _load_required_attr_from_schema(self):
# IF 'required' keyname exists verify it's a boolean,
# if so override default
if self.PROPERTY_KEYNAME_REQUIRED in self.schema:
value = self.schema[self.PROPERTY_KEYNAME_REQUIRED]
if isinstance(value, bool):
self._required = value
else:
valid_values = ', '.join(self.VALID_REQUIRED_VALUES)
attr = self.PROPERTY_KEYNAME_REQUIRED
TOSCAException.generate_inv_schema_property_error(self,
attr,
value,
valid_values)
@property
def status(self):
return self._status
def _load_status_attr_from_schema(self):
# IF 'status' keyname exists verify it's a valid value,
# if so override default
if self.PROPERTY_KEYNAME_STATUS in self.schema:
value = self.schema[self.PROPERTY_KEYNAME_STATUS]
if value in self.VALID_STATUS_VALUES:
self._status = value
else:
valid_values = ', '.join(self.VALID_STATUS_VALUES)
attr = self.PROPERTY_KEYNAME_STATUS
TOSCAException.generate_inv_schema_property_error(self,
attr,
value,
valid_values)

View File

@@ -198,27 +198,31 @@ class EntityTemplate(object):
required_props = []
for p in entitytype.get_properties_def_objects():
allowed_props.append(p.name)
if p.required:
# If property is 'required' and has no 'default' value then record
if p.required and p.default is None:
required_props.append(p.name)
# validate all required properties have values
if properties:
req_props_no_value_or_default = []
self._common_validate_field(properties, allowed_props,
'properties')
# make sure it's not missing any property required by a tosca type
missingprop = []
for r in required_props:
if r not in properties.keys():
missingprop.append(r)
if missingprop:
req_props_no_value_or_default.append(r)
# Required properties found without value or a default value
if req_props_no_value_or_default:
ExceptionCollector.appendException(
MissingRequiredFieldError(
what='"properties" of template "%s"' % self.name,
required=missingprop))
required=req_props_no_value_or_default))
else:
# Required properties in schema, but not in template
if required_props:
ExceptionCollector.appendException(
MissingRequiredFieldError(
what='"properties" of template "%s"' % self.name,
required=missingprop))
required=required_props))
def _validate_field(self, template):
if not isinstance(template, dict):

View File

@@ -0,0 +1,75 @@
# Note: this could eventually be translated to a Neutron Load Balancer
# However, in Heat/HOT the preferred way of doing this is creating an Autoscale Group
#
#heat_template_version: 2015-04-30 ...
#resources:
#load_bal_resource:
# type: OS::Neutron::Pool
# properties:
# admin_state_up: Boolean
# description: String
# lb_method: String
# monitors: [Value, Value, ...]
# name: String
# protocol: String
# provider: String
# subnet: String
# vip: {
# "description": String,
# "name": String,
# "connection_limit": Integer,
# "protocol_port": Integer,
# "subnet": String,
# "address": String,
# "admin_state_up": Boolean,
# "session_persistence":
# {
# "cookie_name": String,
# "type": String}
# }
#
# example from: https://gist.github.com/therve/9231701
#
#resources:
# web_server_group:
# type: AWS::AutoScaling::AutoScalingGroup
# properties:
# AvailabilityZones: [nova]
# LaunchConfigurationName: {get_resource: launch_config}
# MinSize: 1
# MaxSize: 3
# LoadBalancerNames:
# - {get_resource: mylb}
# mypool:
# type: OS::Neutron::Pool
# properties:
# protocol: HTTP
# monitors: [{get_resource: mymonitor}]
# subnet_id: {get_param: subnet_id}
# lb_method: ROUND_ROBIN
# vip:
# protocol_port: 80
# mylb:
# type: OS::Neutron::LoadBalancer
# properties:
# protocol_port: 80
# pool_id: {get_resource: mypool}
tosca_definitions_version: tosca_simple_yaml_1_0
description: Template for deploying a load balancer with predefined endpoint properties.
topology_template:
node_templates:
simple_load_balancer:
type: tosca.nodes.LoadBalancer
capabilities:
# properties:
# algorithm: DEFAULT (define new keyword, ROUND_ROBIN?)
# Client, public facing endpoint
client:
properties:
network_name: PUBLIC
floating: true
dns_name: http://mycompany.com/

View File

@@ -274,7 +274,7 @@ class DataTypeTest(TestCase):
error = self.assertRaises(ValueError, data.validate)
self.assertEqual(_('"1" is not a string.'), error.__str__())
# contact_pone is an invalid field name in nested datatype
# 'contact_pone' is an invalid attribute name in nested datatype below
def test_validation_in_nested_datatype(self):
value_snippet = '''
name: Mike

View File

@@ -248,6 +248,23 @@ class PropertyTest(TestCase):
'value must be one of "%s".') % valid_values)
self.assertEqual(expected_message, str(error))
def test_invalid_property_status(self):
tpl_snippet = '''
properties:
prop:
type: string
status: unknown
'''
schema = yamlparser.simple_parse(tpl_snippet)
error = self.assertRaises(exception.InvalidSchemaError, PropertyDef,
'prop', None, schema['properties']['prop'])
valid_values = ', '.join(PropertyDef.VALID_STATUS_VALUES)
expected_message = (_('Schema definition of "prop" has "status" '
'attribute with invalid value "unknown". The '
'value must be one of "%s".') % valid_values)
self.assertEqual(expected_message, str(error))
def test_capability_proprety_inheritance(self):
tosca_custom_def_example1 = '''
tosca.capabilities.ScalableNew:

View File

@@ -1149,34 +1149,6 @@ custom_types/wordpress.yaml
'are "%s".') % valid_versions))
def test_node_template_capabilities_properties(self):
tpl_snippet = '''
node_templates:
server:
type: tosca.nodes.Compute
capabilities:
host:
properties:
disk_size: 10 GB
num_cpus: { get_input: cpus }
mem_size: 4096 MB
os:
properties:
architecture: x86_64
type: Linux
distribution: Fedora
version: 18.0
scalable:
properties:
min_instances: 1
default_instances: 5
'''
expectedmessage = _('"properties" of template "server" is missing '
'required field "[\'max_instances\']".')
err = self.assertRaises(
exception.MissingRequiredFieldError,
lambda: self._single_node_template_content_test(tpl_snippet))
self.assertEqual(expectedmessage, err.__str__())
# validating capability property values
tpl_snippet = '''
node_templates: