Add the validation for /NetworkDriver.CreateNetwork

This patch adds the validation for /NetworkDriver.CreateNetwork with
the JSON schema.

This patch also introdueces the basic foundation for the following
validations with JSON schemata. The common part of the scheamta is
separated in commons.py and it's injected into each schema.

Change-Id: I1cd849db2eeadb03b4488e1842fbd22e62fff169
Signed-off-by: Taku Fukushima <f.tac.mac@gmail.com>
This commit is contained in:
Taku Fukushima 2015-08-12 19:00:10 +02:00
parent c7e49bb878
commit a1d65edb3f
7 changed files with 190 additions and 5 deletions

View File

@ -14,12 +14,14 @@ import os
from flask import jsonify
from flask import request
from jsonschema import validate
import netaddr
from neutronclient.common import exceptions as n_exceptions
from kuryr import app
from kuryr.constants import SCHEMA
from kuryr import exceptions
from kuryr import schemata
from kuryr import utils
@ -198,7 +200,8 @@ def network_driver_create_network():
json_data = request.get_json(force=True)
app.logger.debug("Received JSON data {0} for /NetworkDriver.CreateNetwork"
.format(json_data))
# TODO(tfukushima): Add a validation of the JSON data for the network.
validate(json_data, schemata.NETWORK_CREATE_SCHEMA)
neutron_network_name = json_data['NetworkID']
network = app.neutron.create_network(

View File

@ -0,0 +1,13 @@
# 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.
from kuryr.schemata.network_create import NETWORK_CREATE_SCHEMA # noqa

112
kuryr/schemata/commons.py Normal file
View File

@ -0,0 +1,112 @@
# 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.
COMMONS = {
u'description': u'Common data schemata shared among other schemata.',
u'links': [],
u'title': u'Kuryr Common Data Schema Definitions',
u'properties': {
u'options': {u'$ref': u'/schemata/commons#/definitions/options'},
u'mac': {u'$ref': u'/schemata/commons#/definitions/mac'},
u'cidrv6': {u'$ref': u'/schemata/commons#/definitions/cidrv6'},
u'interface': {u'$ref': u'/schemata/commons#/definitions/interface'},
u'cidr': {u'$ref': u'/schemata/commons#/definitions/cidr'},
u'id': {u'$ref': u'/schemata/commons#/definitions/id'}
},
u'definitions': {
u'options': {
u'type': u'object',
u'description': u'Options.',
u'example': {}
},
u'mac': {
u'pattern': (u'^((?:[0-9a-f]{2}:){5}[0-9a-f]{2}|'
u'(?:[0-9A-F]{2}:){5}[0-9A-F]{2})$'),
u'type': u'string',
u'description': u'A MAC address.',
u'example': u'aa:bb:cc:dd:ee:ff'
},
u'cidrv6': {
u'pattern': (u'^(('
u'([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|'
u'([0-9a-fA-F]{1,4}:){1,7}:|'
u'([0-9a-fA-F]{1,4}:){1,6}\:[0-9a-fA-F]{1,4}|'
u'([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|'
u'([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|'
u'([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|'
u'([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|'
u'[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|'
u':((:[0-9a-fA-F]{1,4}){1,7}|:)|'
u'fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|'
u'::(ffff(:0{1,4}){0,1}:){0,1}'
u'((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}'
u'(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|'
u'([0-9a-fA-F]{1,4}:){1,4}:'
u'((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}'
u'(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))'
u'(/(1[0-2][0-8]|[1-9]?[0-9])))$'),
u'type': u'string',
u'description': u'A IPv6 CIDR of the subnet'
},
u'interface': {
u'properties': {
u'ID': {
u'description': u'Index of the interface',
u'type': u'number',
},
u'AddressIPv6': {
u'description': u'IPv6 CIDR',
u'$ref': u'#/definitions/commons/definitions/cidrv6'
},
u'MacAddress': {
u'description': u'MAC address',
u'$ref': u'#/definitions/commons/definitions/mac'
},
u'Address': {
u'description': u'IPv4 CIDR',
u'$ref': u'#/definitions/commons/definitions/cidr'
}
},
u'type': u'object',
u'description': u'Interface used in requests against Endpoints.',
u'example': {
u'AddressIPv6': u'fe80::f816:3eff:fe20:57c3/64',
u'MacAddress': u'fa:16:3e:20:57:c3',
u'Address': u'192.168.1.42/24'
}
},
u'cidr': {
u'pattern': (u'^((25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\.){3}'
u'(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])'
u'/(3[0-2]|((1|2)?[0-9]))$'),
u'type': u'string',
u'description': u'A IPv4 CIDR of the subnet.',
u'example': u'10.0.0.0/24'
},
u'id': {
u'pattern': u'^([0-9a-f]{64})$',
u'type': u'string',
u'description': u'256 bits ID value of Docker.',
u'example':
u'51c75a2515d47edecc3f720bb541e287224416fb66715eb7802011d6ffd499f1'
},
u'sandbox_key': {
u'pattern': u'^(/var/run/docker/netns/[0-9a-f]{12})$',
u'type': u'string',
u'description': u'Sandbox information of netns.',
u'example': '/var/run/docker/netns/12bbda391ed0'
}
},
u'$schema': u'http://json-schema.org/draft-04/hyper-schema',
u'type': u'object',
u'id': u'schemata/commons'
}

View File

@ -0,0 +1,40 @@
# 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.
from kuryr.schemata.commons import COMMONS
NETWORK_CREATE_SCHEMA = {
u'links': [{
u'method': u'POST',
u'href': u'/NetworkDriver.CreateNetwork',
u'description': u'Create a Network',
u'rel': u'self',
u'title': u'Create'
}],
u'title': u'Create network',
u'required': [u'NetworkID'],
u'definitions': {u'commons': {}},
u'$schema': u'http://json-schema.org/draft-04/hyper-schema',
u'type': u'object',
u'properties': {
u'NetworkID': {
u'description': u'ID of a Network to be created',
u'$ref': u'#/definitions/commons/definitions/id'
},
u'Options':
{u'type': u'object',
u'description': u'Options',
u'example': {}}
}
}
NETWORK_CREATE_SCHEMA[u'definitions'][u'commons'] = COMMONS

View File

@ -61,6 +61,17 @@ class TestKuryrNetworkCreateFailures(base.TestKuryrFailures):
self.assertEqual(
{'Err': exceptions.Unauthorized.message}, decoded_json)
def test_create_network_bad_request(self):
invalid_docker_network_id = 'id-should-be-hexdigits'
response = self._invoke_create_request(invalid_docker_network_id)
self.assertEqual(400, response.status_code)
decoded_json = jsonutils.loads(response.data)
self.assertTrue('Err' in decoded_json)
# TODO(tfukushima): Add the better error message validation.
self.assertTrue(invalid_docker_network_id in decoded_json['Err'])
self.assertTrue('NetworkID' in decoded_json['Err'])
@ddt
class TestKuryrNetworkDeleteFailures(base.TestKuryrFailures):

View File

@ -11,6 +11,7 @@
# under the License.
from flask import Flask, jsonify
from jsonschema import ValidationError
from neutronclient.common.exceptions import NeutronClientException
from neutronclient.neutron import client
from neutronclient.v2_0 import client as client_v2
@ -54,12 +55,16 @@ def make_json_app(import_name, **kwargs):
app = Flask(import_name, **kwargs)
@app.errorhandler(NeutronClientException)
@app.errorhandler(ValidationError)
def make_json_error(ex):
response = jsonify({"Err": str(ex)})
response.status_code = (ex.code if isinstance(ex, HTTPException)
else ex.status_code
if isinstance(ex, NeutronClientException)
else 500)
response.status_code = 500
if isinstance(ex, HTTPException):
response.status_code = ex.code
elif isinstance(ex, NeutronClientException):
response.status_code = ex.status_code
elif isinstance(ex, ValidationError):
response.status_code = 400
content_type = 'application/vnd.docker.plugins.v1+json; charset=utf-8'
response.headers['Content-Type'] = content_type
return response

View File

@ -5,6 +5,7 @@
pbr<2.0,>=1.3
Babel>=1.3
Flask>=0.10,<1.0
jsonschema!=2.5.0,<3.0.0,>=2.0.0
netaddr>=0.7.12
oslo.serialization>=1.4.0 # Apache-2.0
python-neutronclient>=2.3.11,<3