fuel-plugins/fuel_plugin_builder/validators/schemas/v4.py

395 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from fuel_plugin_builder.validators.schemas import SchemaV3
COMPONENTS_TYPES_STR = '|'.join(
['hypervisor', 'network', 'storage', 'additional_service'])
COMPONENT_NAME_PATTERN = \
'^({0}):([0-9a-z_-]+:)*[0-9a-z_-]+$'.format(COMPONENTS_TYPES_STR)
COMPATIBLE_COMPONENT_NAME_PATTERN = \
'^({0}):([0-9a-z_-]+:)*([0-9a-z_-]+|(\*)?)$'.format(COMPONENTS_TYPES_STR)
TASK_NAME_PATTERN = TASK_ROLE_PATTERN = '^[0-9a-zA-Z_-]+$|^\*$'
NETWORK_ROLE_PATTERN = '^[0-9a-z_-]+$'
FILE_PERMISSIONS_PATTERN = '^[0-7]{4}$'
TASK_VERSION_PATTERN = '^\d+.\d+.\d+$'
STAGE_PATTERN = '^(post_deployment|pre_deployment)' \
'(/[-+]?([0-9]*\.[0-9]+|[0-9]+))?$'
DEFAULT_TASK_ROLE_GROUP_ASSIGNMENT = ('roles', 'groups', 'role')
TASK_OBLIGATORY_FIELDS = ['id', 'type']
class SchemaV4(SchemaV3):
def __init__(self):
super(SchemaV4, self).__init__()
self.role_pattern = TASK_ROLE_PATTERN
@property
def _task_relation(self):
return {
'type': 'object',
'required': ['name'],
'properties': {
'name': {'type': 'string'},
'role': self._task_role,
'policy': {
'type': 'string',
'enum': ['all', 'any']
}
}
}
@property
def _task_role(self):
return {
'oneOf': [
{
'type': 'string',
'format': 'fuel_task_role_format'
},
{
'type': 'array',
'items': {
'type': 'string',
'format': 'fuel_task_role_format'
}
}
]
}
@property
def _task_strategy(self):
return {
'type': 'object',
'properties': {
'type': {
'enum': ['parallel', 'one_by_one']
}
}
}
@property
def _task_stage(self):
return {'type': 'string', 'pattern': STAGE_PATTERN}
@property
def _task_reexecute(self):
return {
'type': 'array',
'items': {
'type': 'string',
'enum': ['deploy_changes']
}
}
def _gen_task_schema(self, task_types, required=None,
parameters=None,
required_any=DEFAULT_TASK_ROLE_GROUP_ASSIGNMENT):
"""Generate deployment task schema using prototype.
:param task_types: task types
:type task_types: str|list
:param required: new required fields
:type required: list
:param parameters: new properties dict
:type parameters: dict
:return:
:rtype: dict
"""
if not task_types:
raise ValueError('Task type should not be empty')
if isinstance(task_types, six.string_types):
task_types = [task_types]
# patch strategy parameter
parameters = parameters or {
"type": "object",
}
parameters.setdefault("properties", {})
parameters["properties"].setdefault("strategy", self._task_strategy)
task_specific_req_fields = list(set(TASK_OBLIGATORY_FIELDS +
(required or [])))
required_fields = []
# Some tasks are ephemeral, so we can leave it as is without target
# groups|role|roles, others must have at least one such field
if required_any:
for group_name_alias in DEFAULT_TASK_ROLE_GROUP_ASSIGNMENT:
required_fields.append({"required": task_specific_req_fields
+ [group_name_alias]})
else:
required_fields.append({"required": task_specific_req_fields})
return {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'properties': {
'type': {'enum': task_types},
'id': {
'type': 'string',
'pattern': TASK_NAME_PATTERN},
'version': {
'type': 'string', "pattern": TASK_VERSION_PATTERN},
'role': self._task_role,
'groups': self._task_role,
'roles': self._task_role,
'required_for': self.task_group,
'requires': self.task_group,
'cross-depends': {
'type': 'array',
'items': self._task_relation},
'cross-depended-by': {
'type': 'array',
'items': self._task_relation},
'stage': self._task_stage,
'tasks': { # used only for 'group' tasks
'type': 'array',
'items': {
'type': 'string',
'pattern': TASK_ROLE_PATTERN}},
'reexecute_on': self._task_reexecute,
'parameters': parameters or {},
},
'anyOf': required_fields
}
@property
def deployment_task_schema(self):
return {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'array',
'items': {
"$ref": "#/definitions/anyTask"
},
"definitions": {
"anyTask": self._gen_task_schema(
[
'copy_files',
'group',
'reboot',
'shell',
'skipped',
'stage',
'sync',
'puppet',
'upload_file',
]
)
}
}
@property
def copy_files_task(self):
return self._gen_task_schema(
"copy_files",
['parameters'],
{
'type': 'object',
'required': ['files'],
'properties': {
'files': {
'type': 'array',
'minItems': 1,
'items': {
'type': 'object',
'required': ['src', 'dst'],
'properties': {
'src': {'type': 'string'},
'dst': {'type': 'string'}}}},
'permissions': {
'type': 'string',
'pattern': FILE_PERMISSIONS_PATTERN},
'dir_permissions': {
'type': 'string',
'pattern': FILE_PERMISSIONS_PATTERN}}})
@property
def group_task(self):
return self._gen_task_schema("group", [])
@property
def puppet_task(self):
return self._gen_task_schema(
"puppet",
[],
{
'type': 'object',
'required': [
'puppet_manifest', 'puppet_modules', 'timeout'],
'properties': {
'puppet_manifest': {
'type': 'string', 'minLength': 1},
'puppet_modules': {
'type': 'string', 'minLength': 1},
'timeout': {'type': 'integer'},
'retries': {'type': 'integer'}
}
}
)
@property
def reboot_task(self):
return self._gen_task_schema(
"reboot",
[],
{
'type': 'object',
'properties': {
'timeout': {'type': 'integer'}
}
}
)
@property
def shell_task(self):
return self._gen_task_schema(
"shell",
[],
{
'type': 'object',
'required': ['cmd'],
'properties': {
'cmd': {
'type': 'string'},
'retries': {
'type': 'integer'},
'interval': {
'type': 'integer'},
'timeout': {
'type': 'integer'}
}
}
)
@property
def skipped_task(self):
return self._gen_task_schema("skipped")
@property
def stage_task(self):
return self._gen_task_schema("stage", required_any=())
@property
def sync_task(self):
return self._gen_task_schema(
"sync",
['parameters'],
{
'type': 'object',
'required': ['src', 'dst'],
'properties': {
'src': {'type': 'string'},
'dst': {'type': 'string'},
'timeout': {'type': 'integer'}
}
}
)
@property
def upload_file_task(self):
return self._gen_task_schema(
"upload_file",
['parameters'],
{
'type': 'object',
'required': ['path', 'data'],
'properties': {
'path': {'type': 'string'},
'data': {'type': 'string'}
}
}
)
@property
def package_version(self):
return {'enum': ['4.0.0']}
@property
def metadata_schema(self):
schema = super(SchemaV4, self).metadata_schema
schema['required'].append('is_hotpluggable')
schema['properties']['is_hotpluggable'] = {'type': 'boolean'}
schema['properties']['groups']['items']['enum'].append('equipment')
return schema
@property
def attr_root_schema(self):
schema = super(SchemaV4, self).attr_root_schema
schema['properties']['attributes']['properties'] = {
'metadata': {
'type': 'object',
'properties': {
'group': {
'enum': [
'general', 'security',
'compute', 'network',
'storage', 'logging',
'openstack_services', 'other'
]
}
}
}
}
return schema
@property
def components_items(self):
return {
'type': 'array',
'items': {
'type': 'object',
'required': ['name'],
'properties': {
'name': {
'type': 'string',
'pattern': COMPATIBLE_COMPONENT_NAME_PATTERN
},
'message': {'type': 'string'}
}
}
}
@property
def components_schema(self):
return {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'array',
'items': {
'required': ['name', 'label'],
'type': 'object',
'additionalProperties': False,
'properties': {
'name': {
'type': 'string',
'pattern': COMPONENT_NAME_PATTERN
},
'label': {'type': 'string'},
'description': {'type': 'string'},
'compatible': self.components_items,
'requires': self.components_items,
'incompatible': self.components_items,
'bind': {'type': 'array'}
}
}
}