summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLucas Alvares Gomes <lucasagomes@gmail.com>2013-11-15 18:39:49 +0000
committerLucas Alvares Gomes <lucasagomes@gmail.com>2013-12-04 10:14:38 +0000
commit282ec175473c46104d4ee951dd5026acb9643fcd (patch)
tree8d7e591fb63fb400bbbcad4497e68ac08f2b8fb5
parent703309e5be784ff82a35cbfe9d1ad51e79987f30 (diff)
downloadironic-282ec175473c46104d4ee951dd5026acb9643fcd.tar.gz
API ports update for WSME 0.5b6 compliance
This makes use of the mandatory option of WSME, to remove some of the custom validation code. The patch also includes a new attribute on Ports API object called node_uuid to store the UUID of the node that Port belongs to, once this field is set it magically converts the UUID to the numeric ID of the node and sets the node_id attribute to be used internally. Change-Id: I973987de48fa008f5752861a2acfaf819f80e2ad Partial-Bug: #1252213
-rw-r--r--ironic/api/controllers/v1/base.py15
-rw-r--r--ironic/api/controllers/v1/port.py114
-rw-r--r--ironic/api/controllers/v1/types.py62
-rw-r--r--ironic/tests/api/test_nodes.py3
-rw-r--r--ironic/tests/api/test_ports.py130
-rw-r--r--ironic/tests/api/test_types.py50
6 files changed, 271 insertions, 103 deletions
diff --git a/ironic/api/controllers/v1/base.py b/ironic/api/controllers/v1/base.py
index da572573b..f1215a864 100644
--- a/ironic/api/controllers/v1/base.py
+++ b/ironic/api/controllers/v1/base.py
@@ -27,6 +27,8 @@ class APIBase(wtypes.Base):
if hasattr(self, k) and
getattr(self, k) != wsme.Unset)
+ # TODO(lucasagomes): Deprecated. Remove it after updating the chassis
+ # and nodes elements
@classmethod
def from_rpc_object(cls, m, fields=None):
"""Convert a RPC object to an API object."""
@@ -37,3 +39,16 @@ class APIBase(wtypes.Base):
for k in obj_dict.keys()
if fields and k not in fields))
return cls(**obj_dict)
+
+ def unset_fields_except(self, except_list=None):
+ """Unset fields so they don't appear in the message body.
+
+ :param except_list: A list of fields that won't be touched.
+
+ """
+ if except_list is None:
+ except_list = []
+
+ for k in self.as_dict():
+ if k not in except_list:
+ setattr(self, k, wsme.Unset)
diff --git a/ironic/api/controllers/v1/port.py b/ironic/api/controllers/v1/port.py
index 53cdbe681..594f8148d 100644
--- a/ironic/api/controllers/v1/port.py
+++ b/ironic/api/controllers/v1/port.py
@@ -28,9 +28,9 @@ import wsmeext.pecan as wsme_pecan
from ironic.api.controllers.v1 import base
from ironic.api.controllers.v1 import collection
from ironic.api.controllers.v1 import link
+from ironic.api.controllers.v1 import types
from ironic.api.controllers.v1 import utils as api_utils
from ironic.common import exception
-from ironic.common import utils
from ironic import objects
from ironic.openstack.common import excutils
from ironic.openstack.common import log
@@ -45,14 +45,40 @@ class Port(base.APIBase):
between the internal object model and the API representation of a port.
"""
- # NOTE: translate 'id' publicly to 'uuid' internally
- uuid = wtypes.text
-
- address = wtypes.text
+ _node_uuid = None
+
+ def _get_node_uuid(self):
+ return self._node_uuid
+
+ def _set_node_uuid(self, value):
+ if value and self._node_uuid != value:
+ try:
+ node = objects.Node.get_by_uuid(pecan.request.context, value)
+ self._node_uuid = node.uuid
+ # NOTE(lucasagomes): Create the node_id attribute on-the-fly
+ # to satisfy the api -> rpc object
+ # conversion.
+ self.node_id = node.id
+ except exception.NodeNotFound as e:
+ # Change error code because 404 (NotFound) is inappropriate
+ # response for a POST request to create a Port
+ e.code = 400 # BadRequest
+ raise e
+ elif value == wtypes.Unset:
+ self._node_uuid = wtypes.Unset
+
+ uuid = types.uuid
+ "Unique UUID for this port"
+
+ address = wsme.wsattr(types.macaddress, mandatory=True)
+ "MAC Address for this port"
extra = {wtypes.text: api_utils.ValidTypes(wtypes.text, six.integer_types)}
+ "This port's meta data"
- node_id = api_utils.ValidTypes(wtypes.text, six.integer_types)
+ node_uuid = wsme.wsproperty(types.uuid, _get_node_uuid, _set_node_uuid,
+ mandatory=True)
+ "The UUID of the node this port belongs to"
links = [link.Link]
"A list containing a self link and associated port links"
@@ -62,16 +88,19 @@ class Port(base.APIBase):
for k in self.fields:
setattr(self, k, kwargs.get(k))
+ # NOTE(lucasagomes): node_uuid is not part of objects.Port.fields
+ # because it's an API-only attribute
+ self.fields.append('node_uuid')
+ setattr(self, 'node_uuid', kwargs.get('node_id', None))
+
@classmethod
def convert_with_links(cls, rpc_port, expand=True):
- fields = ['uuid', 'address'] if not expand else None
- port = Port.from_rpc_object(rpc_port, fields)
+ port = Port(**rpc_port.as_dict())
+ if not expand:
+ port.unset_fields_except(['uuid', 'address'])
- # translate id -> uuid
- if port.node_id and isinstance(port.node_id, six.integer_types):
- node_obj = objects.Node.get_by_uuid(pecan.request.context,
- port.node_id)
- port.node_id = node_obj.uuid
+ # never expose the node_id attribute
+ port.node_id = wtypes.Unset
port.links = [link.Link.make_link('self', pecan.request.host_url,
'ports', port.uuid),
@@ -93,10 +122,11 @@ class PortCollection(collection.Collection):
self._type = 'ports'
@classmethod
- def convert_with_links(cls, ports, limit, url=None,
+ def convert_with_links(cls, rpc_ports, limit, url=None,
expand=False, **kwargs):
collection = PortCollection()
- collection.ports = [Port.convert_with_links(p, expand) for p in ports]
+ collection.ports = [Port.convert_with_links(p, expand)
+ for p in rpc_ports]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
@@ -111,8 +141,8 @@ class PortsController(rest.RestController):
def __init__(self, from_nodes=False):
self._from_nodes = from_nodes
- def _get_ports(self, node_id, marker, limit, sort_key, sort_dir):
- if self._from_nodes and not node_id:
+ def _get_ports(self, node_uuid, marker, limit, sort_key, sort_dir):
+ if self._from_nodes and not node_uuid:
raise exception.InvalidParameterValue(_(
"Node id not specified."))
@@ -124,8 +154,8 @@ class PortsController(rest.RestController):
marker_obj = objects.Port.get_by_uuid(pecan.request.context,
marker)
- if node_id:
- ports = pecan.request.dbapi.get_ports_by_node(node_id, limit,
+ if node_uuid:
+ ports = pecan.request.dbapi.get_ports_by_node(node_uuid, limit,
marker_obj,
sort_key=sort_key,
sort_dir=sort_dir)
@@ -135,27 +165,7 @@ class PortsController(rest.RestController):
sort_dir=sort_dir)
return ports
- def _convert_node_uuid_to_id(self, port_dict):
- # NOTE(lucasagomes): translate uuid -> id, used internally to
- # tune performance
- try:
- node_obj = objects.Node.get_by_uuid(pecan.request.context,
- port_dict['node_id'])
- port_dict['node_id'] = node_obj.id
- except exception.NodeNotFound as e:
- e.code = 400 # BadRequest
- raise e
-
def _check_address(self, port_dict):
- if not utils.is_valid_mac(port_dict['address']):
- if '-' in port_dict['address']:
- msg = _("Does not support hyphens as separator: %s") \
- % port_dict['address']
- else:
- msg = _("Invalid MAC address format: %s") \
- % port_dict['address']
- raise wsme.exc.ClientSideError(msg)
-
try:
if pecan.request.dbapi.get_port(port_dict['address']):
# TODO(whaom) - create a custom SQLAlchemy type like
@@ -168,17 +178,17 @@ class PortsController(rest.RestController):
@wsme_pecan.wsexpose(PortCollection, wtypes.text, wtypes.text, int,
wtypes.text, wtypes.text)
- def get_all(self, node_id=None, marker=None, limit=None,
+ def get_all(self, node_uuid=None, marker=None, limit=None,
sort_key='id', sort_dir='asc'):
"""Retrieve a list of ports."""
- ports = self._get_ports(node_id, marker, limit, sort_key, sort_dir)
+ ports = self._get_ports(node_uuid, marker, limit, sort_key, sort_dir)
return PortCollection.convert_with_links(ports, limit,
sort_key=sort_key,
sort_dir=sort_dir)
@wsme_pecan.wsexpose(PortCollection, wtypes.text, wtypes.text, int,
wtypes.text, wtypes.text)
- def detail(self, node_id=None, marker=None, limit=None,
+ def detail(self, node_uuid=None, marker=None, limit=None,
sort_key='id', sort_dir='asc'):
"""Retrieve a list of ports."""
# NOTE(lucasagomes): /detail should only work agaist collections
@@ -186,7 +196,7 @@ class PortsController(rest.RestController):
if parent != "ports":
raise exception.HTTPNotFound
- ports = self._get_ports(node_id, marker, limit, sort_key, sort_dir)
+ ports = self._get_ports(node_uuid, marker, limit, sort_key, sort_dir)
resource_url = '/'.join(['ports', 'detail'])
return PortCollection.convert_with_links(ports, limit,
url=resource_url,
@@ -209,20 +219,8 @@ class PortsController(rest.RestController):
if self._from_nodes:
raise exception.OperationNotPermitted
- port_dict = port.as_dict()
-
- # Required fields
- missing_attr = [attr for attr in ['address', 'node_id']
- if not port_dict[attr]]
- if missing_attr:
- msg = _("Missing %s attribute(s)")
- raise wsme.exc.ClientSideError(msg % ', '.join(missing_attr))
-
- self._check_address(port_dict)
- self._convert_node_uuid_to_id(port_dict)
-
try:
- new_port = pecan.request.dbapi.create_port(port_dict)
+ new_port = pecan.request.dbapi.create_port(port.as_dict())
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.exception(e)
@@ -253,11 +251,11 @@ class PortsController(rest.RestController):
msg = _("Attribute(s): %s can not be removed")
raise wsme.exc.ClientSideError(msg % ', '.join(missing_attr))
+ # FIXME(lucasagomes): This block should not exist, address should
+ # be unique and validated at the db level.
if port_dict['address'] != patched_port['address']:
self._check_address(patched_port)
- self._convert_node_uuid_to_id(patched_port)
-
defaults = objects.Port.get_defaults()
for key in defaults:
# Internal values that shouldn't be part of the patch
diff --git a/ironic/api/controllers/v1/types.py b/ironic/api/controllers/v1/types.py
new file mode 100644
index 000000000..937752459
--- /dev/null
+++ b/ironic/api/controllers/v1/types.py
@@ -0,0 +1,62 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# coding: utf-8
+#
+# Copyright 2013 Red Hat, Inc.
+# All Rights Reserved.
+#
+# 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 wsme import types as wtypes
+
+from ironic.common import exception
+from ironic.common import utils
+
+
+class MacAddressType(wtypes.UserType):
+ """A simple MAC address type."""
+
+ basetype = wtypes.text
+ name = 'macaddress'
+
+ @staticmethod
+ def validate(value):
+ if not utils.is_valid_mac(value):
+ raise exception.InvalidMAC(mac=value)
+ return value
+
+ @staticmethod
+ def frombasetype(value):
+ return MacAddressType.validate(value)
+
+
+# TODO(lucasagomes): WSME already has one UuidType implementation on trunk,
+# so remove it on the next WSME release (> 0.5b6)
+class UuidType(wtypes.UserType):
+ """A simple UUID type."""
+
+ basetype = wtypes.text
+ name = 'uuid'
+
+ @staticmethod
+ def validate(value):
+ if not utils.is_uuid_like(value):
+ raise exception.InvalidUUID(uuid=value)
+ return value
+
+ @staticmethod
+ def frombasetype(value):
+ return UuidType.validate(value)
+
+
+macaddress = MacAddressType()
+uuid = UuidType()
diff --git a/ironic/tests/api/test_nodes.py b/ironic/tests/api/test_nodes.py
index 9d8dc3338..ea8e18a54 100644
--- a/ironic/tests/api/test_nodes.py
+++ b/ironic/tests/api/test_nodes.py
@@ -496,7 +496,8 @@ class TestPost(base.FunctionalTest):
def test_post_ports_subresource(self):
ndict = dbutils.get_test_node()
self.post_json('/nodes', ndict)
- pdict = dbutils.get_test_port()
+ pdict = dbutils.get_test_port(node_id=None)
+ pdict['node_uuid'] = ndict['uuid']
response = self.post_json('/nodes/ports', pdict,
expect_errors=True)
self.assertEqual(response.status_int, 403)
diff --git a/ironic/tests/api/test_ports.py b/ironic/tests/api/test_ports.py
index 33257b105..8dac1a722 100644
--- a/ironic/tests/api/test_ports.py
+++ b/ironic/tests/api/test_ports.py
@@ -23,6 +23,16 @@ from ironic.tests.api import base
from ironic.tests.db import utils as dbutils
+# NOTE(lucasagomes): When creating a port via API (POST)
+# we have to use node_uuid
+def post_get_test_port(**kw):
+ port = dbutils.get_test_port(**kw)
+ node = dbutils.get_test_node()
+ del port['node_id']
+ port['node_uuid'] = kw.get('node_uuid', node['uuid'])
+ return port
+
+
class TestListPorts(base.FunctionalTest):
def setUp(self):
@@ -40,6 +50,9 @@ class TestListPorts(base.FunctionalTest):
data = self.get_json('/ports')
self.assertEqual(port['uuid'], data['ports'][0]["uuid"])
self.assertNotIn('extra', data['ports'][0])
+ self.assertNotIn('node_uuid', data['ports'][0])
+ # never expose the node_id
+ self.assertNotIn('node_id', data['ports'][0])
def test_detail(self):
pdict = dbutils.get_test_port()
@@ -47,6 +60,9 @@ class TestListPorts(base.FunctionalTest):
data = self.get_json('/ports/detail')
self.assertEqual(port['uuid'], data['ports'][0]["uuid"])
self.assertIn('extra', data['ports'][0])
+ self.assertIn('node_uuid', data['ports'][0])
+ # never expose the node_id
+ self.assertNotIn('node_id', data['ports'][0])
def test_detail_against_single(self):
pdict = dbutils.get_test_port()
@@ -101,31 +117,29 @@ class TestPatch(base.FunctionalTest):
super(TestPatch, self).setUp()
ndict = dbutils.get_test_node()
self.node = self.dbapi.create_node(ndict)
- pdict = dbutils.get_test_port()
- self.post_json('/ports', pdict)
+ self.pdict = post_get_test_port()
+ self.post_json('/ports', self.pdict)
def test_update_byid(self):
- pdict = dbutils.get_test_port()
extra = {'foo': 'bar'}
- response = self.patch_json('/ports/%s' % pdict['uuid'],
+ response = self.patch_json('/ports/%s' % self.pdict['uuid'],
[{'path': '/extra/foo',
'value': 'bar',
'op': 'add'}])
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, 200)
- result = self.get_json('/ports/%s' % pdict['uuid'])
+ result = self.get_json('/ports/%s' % self.pdict['uuid'])
self.assertEqual(result['extra'], extra)
def test_update_byaddress(self):
- pdict = dbutils.get_test_port()
extra = {'foo': 'bar'}
- response = self.patch_json('/ports/%s' % pdict['address'],
+ response = self.patch_json('/ports/%s' % self.pdict['address'],
[{'path': '/extra/foo',
'value': 'bar',
'op': 'add'}])
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, 200)
- result = self.get_json('/ports/%s' % pdict['uuid'])
+ result = self.get_json('/ports/%s' % self.pdict['uuid'])
self.assertEqual(result['extra'], extra)
def test_update_not_found(self):
@@ -140,20 +154,19 @@ class TestPatch(base.FunctionalTest):
self.assertTrue(response.json['error_message'])
def test_replace_singular(self):
- pdict = dbutils.get_test_port()
address = 'AA:BB:CC:DD:EE:FF'
- response = self.patch_json('/ports/%s' % pdict['uuid'],
+ response = self.patch_json('/ports/%s' % self.pdict['uuid'],
[{'path': '/address',
'value': address, 'op': 'replace'}])
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, 200)
- result = self.get_json('/ports/%s' % pdict['uuid'])
+ result = self.get_json('/ports/%s' % self.pdict['uuid'])
self.assertEqual(result['address'], address)
def test_replace_address_already_exist(self):
address = '11:22:33:AA:BB:CC'
- pdict = dbutils.get_test_port(address=address,
- uuid=utils.generate_uuid())
+ pdict = post_get_test_port(address=address,
+ uuid=utils.generate_uuid())
self.post_json('/ports', pdict)
pdict = dbutils.get_test_port()
@@ -166,9 +179,8 @@ class TestPatch(base.FunctionalTest):
self.assertTrue(response.json['error_message'])
def test_replace_nodeid_dont_exist(self):
- pdict = dbutils.get_test_port()
- response = self.patch_json('/ports/%s' % pdict['uuid'],
- [{'path': '/node_id',
+ response = self.patch_json('/ports/%s' % self.pdict['uuid'],
+ [{'path': '/node_uuid',
'value': '12506333-a81c-4d59-9987-889ed5f8687b',
'op': 'replace'}],
expect_errors=True)
@@ -178,9 +190,9 @@ class TestPatch(base.FunctionalTest):
def test_replace_multi(self):
extra = {"foo1": "bar1", "foo2": "bar2", "foo3": "bar3"}
- pdict = dbutils.get_test_port(extra=extra,
- address="AA:BB:CC:DD:EE:FF",
- uuid=utils.generate_uuid())
+ pdict = post_get_test_port(extra=extra,
+ address="AA:BB:CC:DD:EE:FF",
+ uuid=utils.generate_uuid())
self.post_json('/ports', pdict)
new_value = 'new value'
response = self.patch_json('/ports/%s' % pdict['uuid'],
@@ -195,9 +207,9 @@ class TestPatch(base.FunctionalTest):
def test_remove_multi(self):
extra = {"foo1": "bar1", "foo2": "bar2", "foo3": "bar3"}
- pdict = dbutils.get_test_port(extra=extra,
- address="AA:BB:CC:DD:EE:FF",
- uuid=utils.generate_uuid())
+ pdict = post_get_test_port(extra=extra,
+ address="AA:BB:CC:DD:EE:FF",
+ uuid=utils.generate_uuid())
self.post_json('/ports', pdict)
# Removing one item from the collection
@@ -222,8 +234,8 @@ class TestPatch(base.FunctionalTest):
self.assertEqual(result['address'], pdict['address'])
def test_remove_mandatory_field(self):
- pdict = dbutils.get_test_port(address="AA:BB:CC:DD:EE:FF",
- uuid=utils.generate_uuid())
+ pdict = post_get_test_port(address="AA:BB:CC:DD:EE:FF",
+ uuid=utils.generate_uuid())
self.post_json('/ports', pdict)
response = self.patch_json('/ports/%s' % pdict['uuid'],
[{'path': '/address', 'op': 'remove'}],
@@ -233,8 +245,7 @@ class TestPatch(base.FunctionalTest):
self.assertTrue(response.json['error_message'])
def test_add_singular(self):
- pdict = dbutils.get_test_port()
- response = self.patch_json('/ports/%s' % pdict['uuid'],
+ response = self.patch_json('/ports/%s' % self.pdict['uuid'],
[{'path': '/foo', 'value': 'bar',
'op': 'add'}],
expect_errors=True)
@@ -243,27 +254,25 @@ class TestPatch(base.FunctionalTest):
self.assertTrue(response.json['error_message'])
def test_add_multi(self):
- pdict = dbutils.get_test_port()
- response = self.patch_json('/ports/%s' % pdict['uuid'],
+ response = self.patch_json('/ports/%s' % self.pdict['uuid'],
[{'path': '/extra/foo1', 'value': 'bar1',
'op': 'add'},
{'path': '/extra/foo2', 'value': 'bar2',
'op': 'add'}])
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, 200)
- result = self.get_json('/ports/%s' % pdict['uuid'])
+ result = self.get_json('/ports/%s' % self.pdict['uuid'])
expected = {"foo1": "bar1", "foo2": "bar2"}
self.assertEqual(result['extra'], expected)
def test_remove_uuid(self):
- pdict = dbutils.get_test_port()
self.assertRaises(webtest.app.AppError, self.patch_json,
- '/ports/%s' % pdict['uuid'],
+ '/ports/%s' % self.pdict['uuid'],
[{'path': '/uuid', 'op': 'remove'}])
def test_update_byaddress_with_hyphens_delimiter(self):
- pdict = dbutils.get_test_port()
- pdict['address'] = pdict['address'].replace(':', '-')
+ test_address = self.pdict['address'].replace(':', '-')
+ pdict = dbutils.get_test_port(address=test_address)
self.assertRaises(webtest.app.AppError,
self.patch_json,
'/ports/%s' % pdict['address'],
@@ -280,13 +289,13 @@ class TestPost(base.FunctionalTest):
self.node = self.dbapi.create_node(ndict)
def test_create_port(self):
- pdict = dbutils.get_test_port()
+ pdict = post_get_test_port()
self.post_json('/ports', pdict)
result = self.get_json('/ports/%s' % pdict['uuid'])
self.assertEqual(pdict['uuid'], result['uuid'])
def test_create_port_generate_uuid(self):
- pdict = dbutils.get_test_port()
+ pdict = post_get_test_port()
del pdict['uuid']
self.post_json('/ports', pdict)
result = self.get_json('/ports/%s' % pdict['address'])
@@ -294,28 +303,39 @@ class TestPost(base.FunctionalTest):
self.assertTrue(utils.is_uuid_like(result['uuid']))
def test_create_port_valid_extra(self):
- pdict = dbutils.get_test_port(extra={'foo': 123})
+ pdict = post_get_test_port(extra={'foo': 123})
self.post_json('/ports', pdict)
result = self.get_json('/ports/%s' % pdict['uuid'])
self.assertEqual(pdict['extra'], result['extra'])
def test_create_port_invalid_extra(self):
- pdict = dbutils.get_test_port(extra={'foo': 0.123})
+ pdict = post_get_test_port(extra={'foo': 0.123})
self.assertRaises(webtest.app.AppError, self.post_json, '/ports',
pdict)
- def test_create_port_no_mandatory_fields(self):
- pdict = dbutils.get_test_port(address=None, node_id=None)
- self.assertRaises(webtest.app.AppError, self.post_json, '/ports',
- pdict)
+ def test_create_port_no_mandatory_field_address(self):
+ pdict = post_get_test_port()
+ del pdict['address']
+ response = self.post_json('/ports', pdict, expect_errors=True)
+ self.assertEqual(response.status_int, 400)
+ self.assertEqual(response.content_type, 'application/json')
+ self.assertTrue(response.json['error_message'])
+
+ def test_create_port_no_mandatory_field_node_uuid(self):
+ pdict = post_get_test_port()
+ del pdict['node_uuid']
+ response = self.post_json('/ports', pdict, expect_errors=True)
+ self.assertEqual(response.status_int, 400)
+ self.assertEqual(response.content_type, 'application/json')
+ self.assertTrue(response.json['error_message'])
def test_create_port_invalid_addr_format(self):
- pdict = dbutils.get_test_port(address='invalid-format')
+ pdict = post_get_test_port(address='invalid-format')
self.assertRaises(webtest.app.AppError, self.post_json, '/ports',
pdict)
def test_create_port_with_hyphens_delimiter(self):
- pdict = dbutils.get_test_port()
+ pdict = post_get_test_port()
colonsMAC = pdict['address']
hyphensMAC = colonsMAC.replace(':', '-')
pdict['address'] = hyphensMAC
@@ -323,6 +343,28 @@ class TestPost(base.FunctionalTest):
self.post_json,
'/ports', pdict)
+ def test_create_port_invalid_node_uuid_format(self):
+ pdict = post_get_test_port(node_uuid='invalid-format')
+ response = self.post_json('/ports', pdict, expect_errors=True)
+ self.assertEqual(response.content_type, 'application/json')
+ self.assertEqual(response.status_int, 400)
+ self.assertTrue(response.json['error_message'])
+
+ def test_node_uuid_to_node_id_mapping(self):
+ pdict = post_get_test_port(node_uuid=self.node['uuid'])
+ self.post_json('/ports', pdict)
+ # GET doesn't return the node_id it's an internal value
+ port = self.dbapi.get_port(pdict['uuid'])
+ self.assertEqual(self.node['id'], port.node_id)
+
+ def test_create_port_node_uuid_not_found(self):
+ pdict = post_get_test_port(
+ node_uuid='1a1a1a1a-2b2b-3c3c-4d4d-5e5e5e5e5e5e')
+ response = self.post_json('/ports', pdict, expect_errors=True)
+ self.assertEqual(response.content_type, 'application/json')
+ self.assertEqual(response.status_int, 400)
+ self.assertTrue(response.json['error_message'])
+
class TestDelete(base.FunctionalTest):
@@ -330,7 +372,7 @@ class TestDelete(base.FunctionalTest):
super(TestDelete, self).setUp()
ndict = dbutils.get_test_node()
self.node = self.dbapi.create_node(ndict)
- pdict = dbutils.get_test_port()
+ pdict = post_get_test_port()
self.post_json('/ports', pdict)
def test_delete_port_byid(self):
diff --git a/ironic/tests/api/test_types.py b/ironic/tests/api/test_types.py
new file mode 100644
index 000000000..c486ed78b
--- /dev/null
+++ b/ironic/tests/api/test_types.py
@@ -0,0 +1,50 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# coding: utf-8
+#
+# Copyright 2013 Red Hat, Inc.
+# All Rights Reserved.
+#
+# 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 mock
+
+from ironic.api.controllers.v1 import types
+from ironic.common import exception
+from ironic.common import utils
+from ironic.tests.api import base
+
+
+class TestMacAddressType(base.FunctionalTest):
+
+ def test_valid_mac_addr(self):
+ test_mac = 'aa:bb:cc:11:22:33'
+ with mock.patch.object(utils, 'is_valid_mac') as mac_mock:
+ types.MacAddressType.validate(test_mac)
+ mac_mock.assert_called_once_with(test_mac)
+
+ def test_invalid_mac_addr(self):
+ self.assertRaises(exception.InvalidMAC,
+ types.MacAddressType.validate, 'invalid-mac')
+
+
+class TestUuidType(base.FunctionalTest):
+
+ def test_valid_uuid(self):
+ test_uuid = '1a1a1a1a-2b2b-3c3c-4d4d-5e5e5e5e5e5e'
+ with mock.patch.object(utils, 'is_uuid_like') as uuid_mock:
+ types.UuidType.validate(test_uuid)
+ uuid_mock.assert_called_once_with(test_uuid)
+
+ def test_invalid_uuid(self):
+ self.assertRaises(exception.InvalidUUID,
+ types.UuidType.validate, 'invalid-uuid')