From 68bbcb725537d06773ec509a378572c1bf7961fc Mon Sep 17 00:00:00 2001 From: jiangwt100 Date: Sun, 7 Jul 2013 17:34:17 +0800 Subject: [PATCH] Implement port api action Jobs have done in this patch 1. API to retrieve list of ports: Done 2. API to retrieve details of a single port: Done 3. API to create (insert) a new port: Done 4. API to update an existing port: Done 5. API and RPC to delete an existing port: Done 6. DB API of get_port_list and tests Change-Id: Idd8c4ab9bc9e1eac2c0c93dfa699c81070f5fbe8 --- ironic/api/controllers/v1/controller.py | 2 + ironic/api/controllers/v1/port.py | 95 +++++++++++++++++++++++++ ironic/db/api.py | 7 ++ ironic/db/sqlalchemy/api.py | 8 +++ ironic/tests/db/test_ports.py | 26 ++++++- 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 ironic/api/controllers/v1/port.py diff --git a/ironic/api/controllers/v1/controller.py b/ironic/api/controllers/v1/controller.py index bca5924d06..d06b961e36 100644 --- a/ironic/api/controllers/v1/controller.py +++ b/ironic/api/controllers/v1/controller.py @@ -15,9 +15,11 @@ # under the License. from ironic.api.controllers.v1 import node +from ironic.api.controllers.v1 import port class Controller(object): """Version 1 API controller root.""" nodes = node.NodesController() + ports = port.PortsController() diff --git a/ironic/api/controllers/v1/port.py b/ironic/api/controllers/v1/port.py new file mode 100644 index 0000000000..7c1b8ccf68 --- /dev/null +++ b/ironic/api/controllers/v1/port.py @@ -0,0 +1,95 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 UnitedStack 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 pecan +from pecan import rest + +import wsme +from wsme import types as wtypes +import wsmeext.pecan as wsme_pecan + +from ironic.api.controllers.v1 import base +from ironic import objects +from ironic.openstack.common import log + +LOG = log.getLogger(__name__) + + +class Port(base.APIBase): + """API representation of a port. + + This class enforces type checking and value constraints, and converts + 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 + + # NOTE: translate 'extra' internally to 'meta_data' externally + extra = {wtypes.text: wtypes.text} + + node_id = int + + def __init__(self, **kwargs): + self.fields = objects.Port.fields.keys() + for k in self.fields: + setattr(self, k, kwargs.get(k)) + + +class PortsController(rest.RestController): + """REST controller for Ports.""" + + @wsme_pecan.wsexpose([unicode]) + def get_all(self): + """Retrieve a list of ports.""" + return pecan.request.dbapi.get_port_list() + + @wsme_pecan.wsexpose(Port, unicode) + def get_one(self, uuid): + """Retrieve information about the given port.""" + port = objects.Port.get_by_uuid(pecan.request.context, uuid) + return port + + @wsme.validate(Port) + @wsme_pecan.wsexpose(Port, body=Port) + def post(self, port): + """Ceate a new port.""" + try: + new_port = pecan.request.dbapi.create_port(port.as_dict()) + except Exception as e: + LOG.exception(e) + raise wsme.exc.ClientSideError(_("Invalid data")) + return new_port + + @wsme.validate(Port) + @wsme_pecan.wsexpose(Port, unicode, body=Port) + def patch(self, uuid, port_data): + """Update an existing port.""" + # TODO(wentian): add rpc handle, + # eg. if update fails because node is already locked + port = objects.Port.get_by_uuid(pecan.request.context, uuid) + nn_delta_p = port_data.as_terse_dict() + for k in nn_delta_p: + port[k] = nn_delta_p[k] + port.save() + return port + + @wsme_pecan.wsexpose(None, unicode, status_code=204) + def delete(self, port_id): + """Delete a port.""" + pecan.request.dbapi.destroy_port(port_id) diff --git a/ironic/db/api.py b/ironic/db/api.py index 2092bf920e..97c0ce7c11 100644 --- a/ironic/db/api.py +++ b/ironic/db/api.py @@ -170,6 +170,13 @@ class Connection(object): :returns: A port. """ + @abc.abstractmethod + def get_port_list(self): + """Return a lists of port + + :return: Port list. + """ + @abc.abstractmethod def get_ports_by_node(self, node): """List all the ports for a given node. diff --git a/ironic/db/sqlalchemy/api.py b/ironic/db/sqlalchemy/api.py index 02a0981df9..05f2498b48 100644 --- a/ironic/db/sqlalchemy/api.py +++ b/ironic/db/sqlalchemy/api.py @@ -293,6 +293,10 @@ class Connection(api.Connection): def get_port_by_vif(self, vif): pass + def get_port_list(self): + query = model_query(models.Port.uuid) + return [i[0] for i in query.all()] + @objects.objectify(objects.Port) def get_ports_by_node(self, node): query = model_query(models.Port) @@ -302,6 +306,10 @@ class Connection(api.Connection): @objects.objectify(objects.Port) def create_port(self, values): + if not values.get('uuid'): + values['uuid'] = uuidutils.generate_uuid() + if not values.get('extra'): + values['extra'] = '{}' port = models.Port() port.update(values) port.save() diff --git a/ironic/tests/db/test_ports.py b/ironic/tests/db/test_ports.py index a16b613776..caa9a10d30 100644 --- a/ironic/tests/db/test_ports.py +++ b/ironic/tests/db/test_ports.py @@ -19,6 +19,7 @@ from ironic.common import exception from ironic.db import api as dbapi +from ironic.openstack.common import uuidutils from ironic.tests.db import base from ironic.tests.db import utils @@ -33,18 +34,31 @@ class DbPortTestCase(base.DbTestCase): self.n = utils.get_test_node() self.p = utils.get_test_port() - self.dbapi.create_node(self.n) - self.dbapi.create_port(self.p) def test_get_port_by_id(self): + self.dbapi.create_port(self.p) res = self.dbapi.get_port(self.p['id']) self.assertEqual(self.p['address'], res['address']) def test_get_port_by_uuid(self): + self.dbapi.create_port(self.p) res = self.dbapi.get_port(self.p['uuid']) self.assertEqual(self.p['id'], res['id']) + def test_get_port_list(self): + uuids = [] + for i in xrange(1, 6): + n = utils.get_test_port(id=i, uuid=uuidutils.generate_uuid()) + self.dbapi.create_port(n) + uuids.append(unicode(n['uuid'])) + res = self.dbapi.get_port_list() + uuids.sort() + res.sort() + self.assertEqual(uuids, res) + def test_get_port_by_address(self): + self.dbapi.create_port(self.p) + res = self.dbapi.get_port(self.p['address']) self.assertEqual(self.p['id'], res['id']) @@ -56,14 +70,20 @@ class DbPortTestCase(base.DbTestCase): self.dbapi.get_port, 'not-a-mac') def test_get_ports_by_node_id(self): + self.dbapi.create_node(self.n) + self.dbapi.create_port(self.p) res = self.dbapi.get_ports_by_node(self.n['id']) self.assertEqual(self.p['address'], res[0]['address']) def test_get_ports_by_node_uuid(self): + self.dbapi.create_node(self.n) + self.dbapi.create_port(self.p) res = self.dbapi.get_ports_by_node(self.n['uuid']) self.assertEqual(self.p['address'], res[0]['address']) def test_get_ports_by_node_that_does_not_exist(self): + self.dbapi.create_node(self.n) + self.dbapi.create_port(self.p) res = self.dbapi.get_ports_by_node(99) self.assertEqual(0, len(res)) @@ -72,11 +92,13 @@ class DbPortTestCase(base.DbTestCase): self.assertEqual(0, len(res)) def test_destroy_port(self): + self.dbapi.create_port(self.p) self.dbapi.destroy_port(self.p['id']) self.assertRaises(exception.PortNotFound, self.dbapi.destroy_port, self.p['id']) def test_update_port(self): + self.dbapi.create_port(self.p) old_address = self.p['address'] new_address = 'ff.ee.dd.cc.bb.aa'