d318db272a
* global and local antiaffinity support added * dsl doc updated * dsl version bumped to 0.2.0 Change-Id: I832b00e75bc546f7de1446d660f178c58dc3b1b4
354 lines
10 KiB
Python
354 lines
10 KiB
Python
import copy
|
|
from distutils import version
|
|
import logging
|
|
|
|
import fuel_ccp
|
|
from fuel_ccp.validation import base as validation_base
|
|
import jsonschema
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
PATH_RE = r'^(/|((/[\w.-]+)+/?))$'
|
|
FILE_PATH_RE = r'^(/|((/[\w.-]+)+))$'
|
|
|
|
|
|
class ServiceFormatChecker(jsonschema.FormatChecker):
|
|
def __init__(self):
|
|
super(ServiceFormatChecker, self).__init__()
|
|
self.checkers['valid_version'] = (self.valid_version, ())
|
|
|
|
def valid_version(self, entry):
|
|
return version.StrictVersion(entry) is not None
|
|
|
|
|
|
NOT_EMPTY_STRING_SCHEMA = {
|
|
"type": "string",
|
|
"pattern": r"^\s*\S.*$"
|
|
}
|
|
|
|
NOT_EMPTY_STRING_ARRAY_SCHEMA = {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
|
|
"items": NOT_EMPTY_STRING_SCHEMA
|
|
}
|
|
|
|
LOCAL_COMMAND_SCHEMA = {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["command"],
|
|
|
|
"properties": {
|
|
"name": NOT_EMPTY_STRING_SCHEMA,
|
|
"command": NOT_EMPTY_STRING_SCHEMA,
|
|
"dependencies": NOT_EMPTY_STRING_ARRAY_SCHEMA,
|
|
"type": {
|
|
"enum": ["local"]
|
|
},
|
|
"files": NOT_EMPTY_STRING_ARRAY_SCHEMA,
|
|
"user": NOT_EMPTY_STRING_SCHEMA
|
|
}
|
|
}
|
|
|
|
SINGLE_COMMAND_SCHEMA = copy.deepcopy(LOCAL_COMMAND_SCHEMA)
|
|
SINGLE_COMMAND_SCHEMA["required"] = ["name", "command", "type"]
|
|
SINGLE_COMMAND_SCHEMA["properties"]["type"]["enum"] = ["single"]
|
|
|
|
COMMAND_SCHEMA = {
|
|
"type": "object",
|
|
"oneOf": [
|
|
LOCAL_COMMAND_SCHEMA,
|
|
SINGLE_COMMAND_SCHEMA
|
|
]
|
|
}
|
|
|
|
NOT_EMPTY_COMMAND_ARRAY_SCHEMA = {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
|
|
"items": COMMAND_SCHEMA
|
|
}
|
|
|
|
EMPTY_DIR_VOLUME_SCHEMA = {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["name", "path"],
|
|
|
|
"properties": {
|
|
"name": NOT_EMPTY_STRING_SCHEMA,
|
|
"type": {
|
|
"enum": ["empty-dir"]
|
|
},
|
|
"path": {
|
|
"type": "string",
|
|
"pattern": PATH_RE
|
|
},
|
|
"mount-path": {
|
|
"type": "string",
|
|
"pattern": PATH_RE
|
|
},
|
|
"readOnly": {
|
|
"type": "boolean"
|
|
}
|
|
}
|
|
}
|
|
|
|
HOST_VOLUME_SCHEMA = copy.deepcopy(EMPTY_DIR_VOLUME_SCHEMA)
|
|
HOST_VOLUME_SCHEMA["required"] = ["name", "path", "type"]
|
|
HOST_VOLUME_SCHEMA["properties"]["type"]["enum"] = ["host"]
|
|
|
|
VOLUME_SCHEMA = {
|
|
"type": "object",
|
|
"oneOf": [
|
|
EMPTY_DIR_VOLUME_SCHEMA,
|
|
HOST_VOLUME_SCHEMA
|
|
]
|
|
}
|
|
|
|
TIMEOUT_SCHEMA = {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"maximum": 360
|
|
}
|
|
|
|
PROBE_SCHEMA_EXEC = {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["command", "type"],
|
|
|
|
"properties": {
|
|
"type": {
|
|
"enum": ["exec"]
|
|
},
|
|
"command": NOT_EMPTY_STRING_SCHEMA,
|
|
"initialDelay": TIMEOUT_SCHEMA,
|
|
"timeout": TIMEOUT_SCHEMA
|
|
}
|
|
}
|
|
|
|
PORT_SCHEMA = {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"maximum": 65535
|
|
}
|
|
|
|
NODE_PORT_SCHEMA = {
|
|
"type": "integer",
|
|
"minimum": 30000,
|
|
"maximum": 32767
|
|
}
|
|
|
|
PROBE_SCHEMA_HTTP = {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["path", "type", "port"],
|
|
|
|
"properties": {
|
|
"type": {
|
|
"enum": ["httpGet"]
|
|
},
|
|
"port": PORT_SCHEMA,
|
|
"path": NOT_EMPTY_STRING_SCHEMA,
|
|
"initialDelay": TIMEOUT_SCHEMA,
|
|
"timeout": TIMEOUT_SCHEMA
|
|
}
|
|
}
|
|
|
|
PROBE_SCHEMA = {
|
|
"type": "object",
|
|
"oneOf": [
|
|
PROBE_SCHEMA_EXEC,
|
|
PROBE_SCHEMA_HTTP
|
|
]
|
|
}
|
|
|
|
|
|
SERVICE_SCHEMA = {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["dsl_version", "service"],
|
|
|
|
"properties": {
|
|
"dsl_version": {
|
|
"type": "string",
|
|
"format": "valid_version"
|
|
},
|
|
"service": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["name", "containers"],
|
|
|
|
"properties": {
|
|
"name": NOT_EMPTY_STRING_SCHEMA,
|
|
"ports": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
|
|
"items": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["cont"],
|
|
"properties": {
|
|
"cont": PORT_SCHEMA,
|
|
'node': NODE_PORT_SCHEMA,
|
|
"ingress": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"kind": {
|
|
"enum": ["Deployment", "DaemonSet"]
|
|
},
|
|
"hostNetwork": {
|
|
"type": "boolean"
|
|
},
|
|
"hostPID": {
|
|
"type": "boolean"
|
|
},
|
|
"strategy": {
|
|
"enum": ["RollingUpdate", "Recreate"]
|
|
},
|
|
"antiAffinity": {
|
|
"enum": [None, "local", "global"]
|
|
},
|
|
"containers": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
|
|
"items": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["name", "image", "daemon"],
|
|
|
|
"properties": {
|
|
"name": NOT_EMPTY_STRING_SCHEMA,
|
|
"image": NOT_EMPTY_STRING_SCHEMA,
|
|
"privileged": {
|
|
"type": "boolean"
|
|
},
|
|
"probes": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
|
|
"properties": {
|
|
"readiness": {"oneOf": [
|
|
NOT_EMPTY_STRING_SCHEMA,
|
|
PROBE_SCHEMA,
|
|
]},
|
|
"liveness": PROBE_SCHEMA
|
|
}
|
|
},
|
|
"volumes": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
|
|
"items": VOLUME_SCHEMA
|
|
},
|
|
"pre": NOT_EMPTY_COMMAND_ARRAY_SCHEMA,
|
|
"daemon": LOCAL_COMMAND_SCHEMA,
|
|
"post": NOT_EMPTY_COMMAND_ARRAY_SCHEMA,
|
|
"env": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
"items": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["name"],
|
|
|
|
"properties": {
|
|
"name": NOT_EMPTY_STRING_SCHEMA,
|
|
"value": NOT_EMPTY_STRING_SCHEMA,
|
|
"valueFrom": {"type": "object"}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"files": {
|
|
"type": "object",
|
|
"patternProperties": {
|
|
r"^[\w][\w.-]*$": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"required": ["path", "content"],
|
|
|
|
"properties": {
|
|
"path": {
|
|
"type": "string",
|
|
"pattern": FILE_PATH_RE
|
|
},
|
|
"content": NOT_EMPTY_STRING_SCHEMA,
|
|
"perm": {
|
|
"type": "string",
|
|
"pattern": "[0-7]{3,4}"
|
|
},
|
|
"user": NOT_EMPTY_STRING_SCHEMA
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
def validate_service_definitions(components_map, components=None):
|
|
if not components:
|
|
components = components_map.keys()
|
|
else:
|
|
validation_base.validate_components_names(components, components_map)
|
|
|
|
not_passed_components = set()
|
|
|
|
for component in components:
|
|
try:
|
|
jsonschema.validate(components_map[component]["service_content"],
|
|
SERVICE_SCHEMA,
|
|
format_checker=ServiceFormatChecker())
|
|
except jsonschema.ValidationError as e:
|
|
LOG.error("Validation of service definitions for component '%s' "
|
|
"is not passed: '%s'", component, e.message)
|
|
not_passed_components.add(component)
|
|
|
|
if not_passed_components:
|
|
raise RuntimeError(
|
|
"Validation of service definitions for {} of {} components is "
|
|
"not passed.".format(len(not_passed_components), len(components))
|
|
)
|
|
else:
|
|
LOG.info("Service definitions validation passed successfully")
|
|
|
|
|
|
def validate_service_versions(components_map, components=None):
|
|
if not components:
|
|
components = components_map.keys()
|
|
incompatible_services = []
|
|
parser_version = version.StrictVersion(fuel_ccp.dsl_version)
|
|
for component in components:
|
|
service_version = version.StrictVersion(
|
|
components_map[component]['service_content']['dsl_version'])
|
|
if service_version > parser_version:
|
|
LOG.error('%s: Service version validation failed: service version '
|
|
'(%s) greater than parser version (%s)',
|
|
component, str(service_version), str(parser_version))
|
|
incompatible_services.append(component)
|
|
continue
|
|
|
|
service_major_version = service_version.version[0]
|
|
parser_major_version = parser_version.version[0]
|
|
if service_major_version != parser_major_version:
|
|
LOG.error("%s: Service version validation failed: major versions "
|
|
"of service (%s) and parser (%s) are not equal",
|
|
component, str(service_version), str(parser_version))
|
|
incompatible_services.append(component)
|
|
|
|
if incompatible_services:
|
|
raise RuntimeError('The following services have incompatible versions:'
|
|
' %s' % ', '.join(incompatible_services))
|
|
else:
|
|
LOG.info('Service versions validation passed successfully')
|