REST API validation implementation
* All json schemas are implemented * Basic validation is done excepts plugin configs, cinder feature and scaling Implements blueprint savanna-rest-api-1-0-validation Change-Id: I7ff633a8764d17bf3ba49c2362e2dbbedefd61cd
This commit is contained in:
parent
46c95693b4
commit
23234b7a72
@ -35,7 +35,7 @@ def clusters_list():
|
||||
|
||||
|
||||
@rest.post('/clusters')
|
||||
@v.validate(v_c.cluster_schema, v_c.check_cluster_create)
|
||||
@v.validate(v_c.CLUSTER_SCHEMA, v_c.check_cluster_create)
|
||||
def clusters_create(data):
|
||||
return u.render(api.create_cluster(data).wrapped_dict)
|
||||
|
||||
@ -68,7 +68,7 @@ def cluster_templates_list():
|
||||
|
||||
|
||||
@rest.post('/cluster-templates')
|
||||
@v.validate(v_ct.cluster_template_schema, v_ct.check_cluster_template_create)
|
||||
@v.validate(v_ct.CLUSTER_TEMPLATE_SCHEMA, v_ct.check_cluster_template_create)
|
||||
def cluster_templates_create(data):
|
||||
return u.render(api.create_cluster_template(data).wrapped_dict)
|
||||
|
||||
@ -102,7 +102,7 @@ def node_group_templates_list():
|
||||
|
||||
|
||||
@rest.post('/node-group-templates')
|
||||
@v.validate(v_ngt.node_group_template_schema,
|
||||
@v.validate(v_ngt.NODE_GROUP_TEMPLATE_SCHEMA,
|
||||
v_ngt.check_node_group_template_create)
|
||||
def node_group_templates_create(data):
|
||||
return u.render(api.create_node_group_template(data).wrapped_dict)
|
||||
|
@ -40,3 +40,23 @@ class NotFoundException(SavannaException):
|
||||
self.value = value
|
||||
if message:
|
||||
self.message = message % value
|
||||
|
||||
|
||||
class NameAlreadyExistsException(SavannaException):
|
||||
|
||||
message = "Name already exists"
|
||||
|
||||
def __init__(self, message=None):
|
||||
self.code = "NAME_ALREADY_EXISTS"
|
||||
if message:
|
||||
self.message = message
|
||||
|
||||
|
||||
class InvalidException(SavannaException):
|
||||
|
||||
message = "Invalid object reference"
|
||||
|
||||
def __init__(self, message=None):
|
||||
self.code = "INVALID_REFERENCE"
|
||||
if message:
|
||||
self.message = message
|
||||
|
93
savanna/service/validations/base.py
Normal file
93
savanna/service/validations/base.py
Normal file
@ -0,0 +1,93 @@
|
||||
# Copyright (c) 2013 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 novaclient.exceptions as nova_ex
|
||||
|
||||
import savanna.exceptions as ex
|
||||
import savanna.plugins.base as plugin_base
|
||||
import savanna.service.api as api
|
||||
import savanna.utils.openstack.nova as nova
|
||||
|
||||
|
||||
# Common validation checks
|
||||
def check_plugin_name_exists(name):
|
||||
if name not in [p.name for p in api.get_plugins()]:
|
||||
raise ex.InvalidException("Savanna doesn't contain plugin with name %s"
|
||||
% name)
|
||||
|
||||
|
||||
def check_plugin_supports_version(p_name, version):
|
||||
if version not in plugin_base.PLUGINS.get_plugin(p_name).get_versions():
|
||||
raise ex.InvalidException("Requested plugin '%s' doesn't support"
|
||||
" version '%s'" % (p_name, version))
|
||||
|
||||
|
||||
def check_image_exists(image_id):
|
||||
try:
|
||||
# TODO(aignatov): Check supported images by plugin instead of it
|
||||
api.get_image(id=image_id)
|
||||
except nova_ex.NotFound:
|
||||
raise ex.InvalidException("Requested image '%s' not found"
|
||||
% image_id)
|
||||
|
||||
|
||||
def check_flavor_exists(flavor_id):
|
||||
try:
|
||||
nova.client().flavors.get(flavor_id)
|
||||
except nova_ex.NotFound:
|
||||
raise ex.InvalidException("Requested flavor '%s' not found"
|
||||
% flavor_id)
|
||||
|
||||
|
||||
def check_node_processes(plugin_name, version, node_processes):
|
||||
if len(set(node_processes)) != len(node_processes):
|
||||
raise ex.InvalidException("Duplicates in node processes "
|
||||
"have been detected")
|
||||
plugin_procesess = []
|
||||
for process in plugin_base.PLUGINS.get_plugin(
|
||||
plugin_name).get_node_processes(version).values():
|
||||
plugin_procesess += process
|
||||
|
||||
if not set(node_processes).issubset(set(plugin_procesess)):
|
||||
raise ex.InvalidException("Plugin supports the following "
|
||||
"node procesess: " % plugin_procesess)
|
||||
|
||||
|
||||
# Cluster creation related checks
|
||||
def check_cluster_unique_name(name):
|
||||
if name in [cluster.name for cluster in api.get_clusters()]:
|
||||
raise ex.NameAlreadyExistsException("Cluster with name '%s' already"
|
||||
" exists" % name)
|
||||
|
||||
|
||||
def check_keypair_exists(keypair):
|
||||
try:
|
||||
nova.client().keypairs.get(keypair)
|
||||
except nova_ex.NotFound:
|
||||
raise ex.InvalidException("Requested keypair '%s' not found" % keypair)
|
||||
|
||||
|
||||
# Cluster templates creation related checks
|
||||
def check_cluster_template_unique_name(name):
|
||||
if name in [t.name for t in api.get_cluster_templates()]:
|
||||
raise ex.NameAlreadyExistsException("Cluster template with name '%s'"
|
||||
" already exists" % name)
|
||||
|
||||
|
||||
# NodeGroup templates related checks
|
||||
def check_node_group_template_unique_name(name):
|
||||
if name in [t.name for t in api.get_node_group_templates()]:
|
||||
raise ex.NameAlreadyExistsException("NodeGroup template with name '%s'"
|
||||
" already exists" % name)
|
@ -13,10 +13,91 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import copy
|
||||
|
||||
cluster_template_schema = {
|
||||
import savanna.service.validations.base as b
|
||||
import savanna.service.validations.node_group_templates as ng_tml
|
||||
|
||||
|
||||
def _build_ng_schema_for_cluster_tmpl():
|
||||
cl_tmpl_ng_schema = copy.deepcopy(ng_tml.NODE_GROUP_TEMPLATE_SCHEMA)
|
||||
cl_tmpl_ng_schema['properties'].update({"count": {"type": "integer"}})
|
||||
cl_tmpl_ng_schema["required"] = ['name', 'flavor_id',
|
||||
'node_processes', 'count']
|
||||
del cl_tmpl_ng_schema['properties']['hadoop_version']
|
||||
del cl_tmpl_ng_schema['properties']['plugin_name']
|
||||
return cl_tmpl_ng_schema
|
||||
|
||||
|
||||
_cluster_tmpl_ng_schema = _build_ng_schema_for_cluster_tmpl()
|
||||
|
||||
|
||||
def _build_ng_tmpl_schema_for_cluster_template():
|
||||
cl_tmpl_ng_tmpl_schema = copy.deepcopy(_cluster_tmpl_ng_schema)
|
||||
cl_tmpl_ng_tmpl_schema['properties'].update(
|
||||
{
|
||||
"node_group_template_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
}
|
||||
})
|
||||
cl_tmpl_ng_tmpl_schema["required"] = ["node_group_template_id",
|
||||
"name", "count"]
|
||||
return cl_tmpl_ng_tmpl_schema
|
||||
|
||||
_cluster_tmpl_ng_tmpl_schema = _build_ng_tmpl_schema_for_cluster_template()
|
||||
|
||||
|
||||
CLUSTER_TEMPLATE_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50,
|
||||
"format": "valid_name",
|
||||
},
|
||||
"plugin_name": {
|
||||
"type": "string",
|
||||
},
|
||||
"hadoop_version": {
|
||||
"type": "string",
|
||||
},
|
||||
"default_image_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
},
|
||||
"cluster_configs": {
|
||||
"type": "configs",
|
||||
},
|
||||
"node_groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [_cluster_tmpl_ng_tmpl_schema,
|
||||
_cluster_tmpl_ng_schema]
|
||||
}
|
||||
},
|
||||
"anti_affinity_group": {
|
||||
"type": "string",
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": [
|
||||
"name",
|
||||
"plugin_name",
|
||||
"hadoop_version",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def check_cluster_template_create(data):
|
||||
pass
|
||||
b.check_cluster_template_unique_name(data['name'])
|
||||
b.check_plugin_name_exists(data['plugin_name'])
|
||||
b.check_plugin_supports_version(data['plugin_name'],
|
||||
data['hadoop_version'])
|
||||
|
||||
if data.get('default_image_id'):
|
||||
b.check_image_exists(data['default_image_id'])
|
||||
|
@ -13,9 +13,37 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cluster_schema = {
|
||||
}
|
||||
import copy
|
||||
|
||||
import savanna.service.validations.base as b
|
||||
import savanna.service.validations.cluster_templates as cl_tmpl
|
||||
|
||||
|
||||
def _build_cluster_schema():
|
||||
cluster_schema = copy.deepcopy(cl_tmpl.CLUSTER_TEMPLATE_SCHEMA)
|
||||
cluster_schema['properties']['name']['format'] = "hostname"
|
||||
cluster_schema['properties'].update({
|
||||
"user_keypair_id": {
|
||||
"type": "string",
|
||||
"format": "valid_name",
|
||||
},
|
||||
"cluster_template_id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
}})
|
||||
return cluster_schema
|
||||
|
||||
CLUSTER_SCHEMA = _build_cluster_schema()
|
||||
|
||||
|
||||
def check_cluster_create(data):
|
||||
pass
|
||||
b.check_cluster_unique_name(data['name'])
|
||||
b.check_plugin_name_exists(data['plugin_name'])
|
||||
b.check_plugin_supports_version(data['plugin_name'],
|
||||
data['hadoop_version'])
|
||||
|
||||
if data.get('user_keypair_id'):
|
||||
b.check_keypair_exists(data['user_keypair_id'])
|
||||
|
||||
if data.get('default_image_id'):
|
||||
b.check_image_exists(data['default_image_id'])
|
||||
|
@ -13,11 +13,16 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
node_group_template_schema = {
|
||||
import savanna.service.validations.base as b
|
||||
|
||||
NODE_GROUP_TEMPLATE_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50,
|
||||
"format": "valid_name",
|
||||
},
|
||||
"flavor_id": {
|
||||
'type': 'flavor',
|
||||
@ -32,7 +37,8 @@ node_group_template_schema = {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
}
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"image_id": {
|
||||
"type": "string",
|
||||
@ -68,4 +74,13 @@ node_group_template_schema = {
|
||||
|
||||
|
||||
def check_node_group_template_create(data):
|
||||
pass
|
||||
b.check_node_group_template_unique_name(data['name'])
|
||||
b.check_plugin_name_exists(data['plugin_name'])
|
||||
b.check_plugin_supports_version(data['plugin_name'],
|
||||
data['hadoop_version'])
|
||||
b.check_flavor_exists(data['flavor_id'])
|
||||
b.check_node_processes(data['plugin_name'], data['hadoop_version'],
|
||||
data['node_processes'])
|
||||
|
||||
if data.get('image_id'):
|
||||
b.check_image_exists(data['image_id'])
|
||||
|
@ -13,12 +13,22 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
|
||||
import jsonschema
|
||||
import six
|
||||
|
||||
from savanna.openstack.common import uuidutils
|
||||
|
||||
|
||||
@jsonschema.FormatChecker.cls_checks('valid_name')
|
||||
def validate_name_format(entry):
|
||||
res = re.match(r"^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-_]"
|
||||
r"*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z]"
|
||||
r"[A-Za-z0-9\-_]*[A-Za-z0-9])$", entry)
|
||||
return res is not None
|
||||
|
||||
|
||||
@jsonschema.FormatChecker.cls_checks('uuid')
|
||||
def validate_uuid_format(entry):
|
||||
return uuidutils.is_uuid_like(entry)
|
||||
|
Loading…
Reference in New Issue
Block a user