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')
|
@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):
|
def clusters_create(data):
|
||||||
return u.render(api.create_cluster(data).wrapped_dict)
|
return u.render(api.create_cluster(data).wrapped_dict)
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ def cluster_templates_list():
|
|||||||
|
|
||||||
|
|
||||||
@rest.post('/cluster-templates')
|
@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):
|
def cluster_templates_create(data):
|
||||||
return u.render(api.create_cluster_template(data).wrapped_dict)
|
return u.render(api.create_cluster_template(data).wrapped_dict)
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ def node_group_templates_list():
|
|||||||
|
|
||||||
|
|
||||||
@rest.post('/node-group-templates')
|
@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)
|
v_ngt.check_node_group_template_create)
|
||||||
def node_group_templates_create(data):
|
def node_group_templates_create(data):
|
||||||
return u.render(api.create_node_group_template(data).wrapped_dict)
|
return u.render(api.create_node_group_template(data).wrapped_dict)
|
||||||
|
@ -40,3 +40,23 @@ class NotFoundException(SavannaException):
|
|||||||
self.value = value
|
self.value = value
|
||||||
if message:
|
if message:
|
||||||
self.message = message % value
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# 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):
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# 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):
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
node_group_template_schema = {
|
import savanna.service.validations.base as b
|
||||||
|
|
||||||
|
NODE_GROUP_TEMPLATE_SCHEMA = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 50,
|
||||||
|
"format": "valid_name",
|
||||||
},
|
},
|
||||||
"flavor_id": {
|
"flavor_id": {
|
||||||
'type': 'flavor',
|
'type': 'flavor',
|
||||||
@ -32,7 +37,8 @@ node_group_template_schema = {
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
}
|
},
|
||||||
|
"minItems": 1
|
||||||
},
|
},
|
||||||
"image_id": {
|
"image_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -68,4 +74,13 @@ node_group_template_schema = {
|
|||||||
|
|
||||||
|
|
||||||
def check_node_group_template_create(data):
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from savanna.openstack.common import uuidutils
|
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')
|
@jsonschema.FormatChecker.cls_checks('uuid')
|
||||||
def validate_uuid_format(entry):
|
def validate_uuid_format(entry):
|
||||||
return uuidutils.is_uuid_like(entry)
|
return uuidutils.is_uuid_like(entry)
|
||||||
|
Loading…
Reference in New Issue
Block a user