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:
Alexander Ignatov 2013-07-04 19:43:09 +04:00 committed by Gerrit Code Review
parent 46c95693b4
commit 23234b7a72
7 changed files with 258 additions and 11 deletions

View File

@ -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)

View File

@ -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

View 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)

View File

@ -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'])

View File

@ -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'])

View File

@ -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'])

View File

@ -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)