]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
blueprint api-operational-status
authorSalvatore Orlando <salvatore.orlando@eu.citrix.com>
Mon, 5 Dec 2011 17:28:33 +0000 (17:28 +0000)
committerSalvatore Orlando <salvatore.orlando@eu.citrix.com>
Tue, 13 Dec 2011 10:51:44 +0000 (10:51 +0000)
Adds a new attribute expressing current operational status for port and network resources

Also includes:
- db models: changes to accomodate operational status concept
- unit tests: changes to include different validation functions for API v1.0 and v.1.1

This changeset does not include changes to the client library

NOTE: Addressing issue concerning unit tests for OVS plugin. API unit tests launched with
PLUGIN_DIR set to Cisco's plugin work fine as well.

Change-Id: I0c4f0f8a8c392bae75c668c28070364ca230f965

12 files changed:
quantum/api/api_common.py
quantum/api/networks.py
quantum/api/ports.py
quantum/api/views/networks.py
quantum/api/views/ports.py
quantum/common/test_lib.py
quantum/db/api.py
quantum/db/models.py
quantum/plugins/openvswitch/run_tests.py
quantum/plugins/sample/SamplePlugin.py
quantum/tests/unit/_test_api.py
quantum/tests/unit/test_api.py

index d3a6c974559dd7f4fdd3dd13be8a9524077c216d..96fe70a208040762c1e1562b7677e0ec9798523c 100644 (file)
@@ -27,6 +27,22 @@ XML_NS_V11 = 'http://openstack.org/quantum/api/v1.1'
 LOG = logging.getLogger('quantum.api.api_common')
 
 
+class OperationalStatus:
+    """ Enumeration for operational status
+
+        UP : the resource is available (operationall up)
+        DOWN : the resource is not operational; this might indicate
+               a failure in the underlying switching fabric.
+        PROVISIONING: the plugin is creating or updating the resource
+                      in the underlying switching fabric
+        UNKNOWN: the plugin does not support the operational status concept.
+    """
+    UP = "UP"
+    DOWN = "DOWN"
+    PROVISIONING = "PROVISIONING"
+    UNKNOWN = "UNKNOWN"
+
+
 def create_resource(version, controller_dict):
     """
     Generic function for creating a wsgi resource
index a770c24d1fe74d730e1e5445872ec223eef6521a..50b1e0aa71da3542dd5787e1f49a10854f00a0a3 100644 (file)
@@ -146,12 +146,18 @@ class ControllerV10(Controller):
 
 
 class ControllerV11(Controller):
-    """Network resources controller for Quantum v1.1 API"""
+    """Network resources controller for Quantum v1.1 API
+
+       Note: at this state this class only adds serialization
+       metadata for the operational status concept.
+       API filters, pagination, and atom links will be handled by
+       this class as well.
+    """
 
     _serialization_metadata = {
             "attributes": {
-                "network": ["id", "name"],
-                "port": ["id", "state"],
+                "network": ["id", "name", "op-status"],
+                "port": ["id", "state", "op-status"],
                 "attachment": ["id"]},
             "plurals": {"networks": "network",
                         "ports": "port"}
index d0d69f52d3a66a2d3de66bc5a988af4393f3d062..fb5a578646597089b855b23c8c8159ac470bb3c3 100644 (file)
@@ -50,7 +50,7 @@ class Controller(common.QuantumController):
                port_details=False):
         """ Returns a list of ports. """
         port_list = self._plugin.get_all_ports(tenant_id, network_id)
-        builder = ports_view.get_view_builder(request)
+        builder = ports_view.get_view_builder(request, self.version)
 
         # Load extra data for ports if required.
         if port_details:
@@ -69,7 +69,7 @@ class Controller(common.QuantumController):
         """ Returns a specific port. """
         port = self._plugin.get_port_details(
                         tenant_id, network_id, port_id)
-        builder = ports_view.get_view_builder(request)
+        builder = ports_view.get_view_builder(request, self.version)
         result = builder.build(port, port_details=True,
                                att_details=att_details)['port']
         return dict(port=result)
@@ -111,7 +111,7 @@ class Controller(common.QuantumController):
         port = self._plugin.create_port(tenant_id,
                                         network_id, body['port']['state'],
                                         **body)
-        builder = ports_view.get_view_builder(request)
+        builder = ports_view.get_view_builder(request, self.version)
         result = builder.build(port)['port']
         return dict(port=result)
 
@@ -151,7 +151,7 @@ class ControllerV11(Controller):
 
     _serialization_metadata = {
             "attributes": {
-                "port": ["id", "state"],
+                "port": ["id", "state", "op-status"],
                 "attachment": ["id"]},
             "plurals": {"ports": "port"}
     }
index 1ed858e50a331fef3ddffa7aa6f33f05eedd4a9a..40b0b35bdeb3e9dec8bb6aeb3416a24165575f62 100644 (file)
@@ -15,6 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from quantum.api.api_common import OperationalStatus
+
 
 def get_view_builder(req, version):
     base_url = req.application_url
@@ -64,6 +66,26 @@ class ViewBuilder10(object):
 
 
 class ViewBuilder11(ViewBuilder10):
-    #TODO(salvatore-orlando): will extend for Operational status
-    # in appropriate branch
-    pass
+
+    def _build_simple(self, network_data):
+        """Return a simple model of a network."""
+        return dict(network=dict(id=network_data['net-id']))
+
+    def _build_detail(self, network_data):
+        """Return a detailed model of a network. """
+        op_status = network_data.get('net-op-status',
+                                     OperationalStatus.UNKNOWN)
+        return dict(network={'id': network_data['net-id'],
+                             'name': network_data['net-name'],
+                             'op-status': op_status})
+
+    def _build_port(self, port_data):
+        """Return details about a specific logical port."""
+        op_status = port_data.get('port-op-status',
+                                    OperationalStatus.UNKNOWN)
+        port_dict = {'id': port_data['port-id'],
+                     'state': port_data['port-state'],
+                     'op-status': op_status}
+        if port_data['attachment']:
+            port_dict['attachment'] = dict(id=port_data['attachment'])
+        return port_dict
index dd3f18ea236b7592bb13fa37003d637aaa21a123..919e9f5c4933029426fafdc5fb91da041307bf08 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from quantum.api.api_common import OperationalStatus
 
-def get_view_builder(req):
+
+def get_view_builder(req, version):
     base_url = req.application_url
-    return ViewBuilder(base_url)
+    view_builder = {
+        '1.0': ViewBuilder10,
+        '1.1': ViewBuilder11,
+    }[version](base_url)
+    return view_builder
 
 
-class ViewBuilder(object):
+class ViewBuilder10(object):
 
     def __init__(self, base_url=None):
         """
@@ -37,3 +43,17 @@ class ViewBuilder(object):
         if att_details and port_data['attachment']:
             port['port']['attachment'] = dict(id=port_data['attachment'])
         return port
+
+
+class ViewBuilder11(ViewBuilder10):
+
+    def build(self, port_data, port_details=False, att_details=False):
+        """Generates a port entity with operation status info"""
+        port = dict(port=dict(id=port_data['port-id']))
+        if port_details:
+            port['port']['state'] = port_data['port-state']
+            port['port']['op-status'] = port_data.get('port-op-status',
+                                        OperationalStatus.UNKNOWN)
+        if att_details and port_data['attachment']:
+            port['port']['attachment'] = dict(id=port_data['attachment'])
+        return port
index 9df0574c7b6a8bce20a35a79ec4e0cce087b97aa..03578817d6a9cef930af7074bb3d612b92885c90 100644 (file)
@@ -286,4 +286,6 @@ def run_tests(c=None):
 # quantum/plugins/openvswitch/ )
 test_config = {
     "plugin_name": "quantum.plugins.sample.SamplePlugin.FakePlugin",
+    "default_net_op_status": "UP",
+    "default_port_op_status": "UP",
 }
index d262f8acf18be4a94c50955f67be62c2a9d5bf17..84d5b9f53f2a9cc93c6f2db4417ba9aec2ac9f80 100644 (file)
@@ -22,6 +22,7 @@ import logging
 from sqlalchemy import create_engine
 from sqlalchemy.orm import sessionmaker, exc
 
+from quantum.api.api_common import OperationalStatus
 from quantum.common import exceptions as q_exc
 from quantum.db import models
 
@@ -80,11 +81,11 @@ def unregister_models():
     BASE.metadata.drop_all(_ENGINE)
 
 
-def network_create(tenant_id, name):
+def network_create(tenant_id, name, op_status=OperationalStatus.UNKNOWN):
     session = get_session()
 
     with session.begin():
-        net = models.Network(tenant_id, name)
+        net = models.Network(tenant_id, name, op_status)
         session.add(net)
         session.flush()
         return net
@@ -137,13 +138,13 @@ def network_destroy(net_id):
         raise q_exc.NetworkNotFound(net_id=net_id)
 
 
-def port_create(net_id, state=None):
+def port_create(net_id, state=None, op_status=OperationalStatus.UNKNOWN):
     # confirm network exists
     network_get(net_id)
 
     session = get_session()
     with session.begin():
-        port = models.Port(net_id)
+        port = models.Port(net_id, op_status)
         port['state'] = state or 'DOWN'
         session.add(port)
         session.flush()
index 409e91dea1910f981473e10005fe9b3b6f1a47f9..1039cbe2199bfe14bdaf25d332e09b15260ad94f 100644 (file)
 # @author: Somik Behera, Nicira Networks, Inc.
 # @author: Brad Hall, Nicira Networks, Inc.
 # @author: Dan Wendlandt, Nicira Networks, Inc.
+# @author: Salvatore Orlando, Citrix Systems
 
 import uuid
 
-
 from sqlalchemy import Column, String, ForeignKey
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import relation, object_mapper
 
+from quantum.api import api_common as common
 
 BASE = declarative_base()
 
@@ -73,16 +74,20 @@ class Port(BASE, QuantumBase):
     interface_id = Column(String(255), nullable=True)
     # Port state - Hardcoding string value at the moment
     state = Column(String(8))
+    op_status = Column(String(16))
 
-    def __init__(self, network_id):
+    def __init__(self, network_id,
+                 op_status=common.OperationalStatus.UNKNOWN):
         self.uuid = str(uuid.uuid4())
         self.network_id = network_id
         self.interface_id = None
         self.state = "DOWN"
+        self.op_status = op_status
 
     def __repr__(self):
-        return "<Port(%s,%s,%s,%s)>" % (self.uuid, self.network_id,
-                                     self.state, self.interface_id)
+        return "<Port(%s,%s,%s,%s,%s)>" % (self.uuid, self.network_id,
+                                           self.state, self.op_status,
+                                           self.interface_id)
 
 
 class Network(BASE, QuantumBase):
@@ -93,12 +98,15 @@ class Network(BASE, QuantumBase):
     tenant_id = Column(String(255), nullable=False)
     name = Column(String(255))
     ports = relation(Port, order_by=Port.uuid, backref="network")
+    op_status = Column(String(16))
 
-    def __init__(self, tenant_id, name):
+    def __init__(self, tenant_id, name,
+                 op_status=common.OperationalStatus.UNKNOWN):
         self.uuid = str(uuid.uuid4())
         self.tenant_id = tenant_id
         self.name = name
+        self.op_status = op_status
 
     def __repr__(self):
-        return "<Network(%s,%s,%s)>" % \
-          (self.uuid, self.name, self.tenant_id)
+        return "<Network(%s,%s,%s,%s)>" % \
+          (self.uuid, self.name, self.op_status, self.tenant_id)
index 0377dd8943dface31d455aa60e6a07510d69a231..9649efa0d8723db19aa31982f1a258d7854eac03 100644 (file)
@@ -49,7 +49,12 @@ if __name__ == '__main__':
     # we should only invoked the tests once
     invoke_once = len(sys.argv) > 1
 
+    # NOTE (salvatore-orlando):
+    # please update default values for operational status according to
+    # the plugin behavior.
     test_config['plugin_name'] = "ovs_quantum_plugin.OVSQuantumPlugin"
+    test_config['default_net_op_status'] = "UNKNOWN"
+    test_config['default_port_op_status'] = "UNKNOWN"
 
     cwd = os.getcwd()
     c = config.Config(stream=sys.stdout,
index 2f5626609d33c5a283bdc3b71ee34d7fc8d96383..5fb6cbc812cf745fe52d8bec3b7469dd4d17437f 100644 (file)
@@ -18,6 +18,7 @@
 
 import logging
 
+from quantum.api.api_common import OperationalStatus
 from quantum.common import exceptions as exc
 from quantum.db import api as db
 
@@ -174,7 +175,8 @@ class FakePlugin(object):
         nets = []
         for net in db.network_list(tenant_id):
             net_item = {'net-id': str(net.uuid),
-                        'net-name': net.name}
+                        'net-name': net.name,
+                        'net-op-status': net.op_status}
             nets.append(net_item)
         return nets
 
@@ -189,6 +191,7 @@ class FakePlugin(object):
         ports = self.get_all_ports(tenant_id, net_id)
         return {'net-id': str(net.uuid),
                 'net-name': net.name,
+                'net-op-status': net.op_status,
                 'net-ports': ports}
 
     def create_network(self, tenant_id, net_name, **kwargs):
@@ -198,8 +201,11 @@ class FakePlugin(object):
         """
         LOG.debug("FakePlugin.create_network() called")
         new_net = db.network_create(tenant_id, net_name)
+        # Put operational status UP
+        db.network_update(new_net.uuid, net_name,
+                          op_status=OperationalStatus.UP)
         # Return uuid for newly created network as net-id.
-        return {'net-id': new_net['uuid']}
+        return {'net-id': new_net.uuid}
 
     def delete_network(self, tenant_id, net_id):
         """
@@ -248,7 +254,8 @@ class FakePlugin(object):
         port = self._get_port(tenant_id, net_id, port_id)
         return {'port-id': str(port.uuid),
                 'attachment': port.interface_id,
-                'port-state': port.state}
+                'port-state': port.state,
+                'port-op-status': port.op_status}
 
     def create_port(self, tenant_id, net_id, port_state=None, **kwargs):
         """
@@ -258,6 +265,9 @@ class FakePlugin(object):
         # verify net_id
         self._get_network(tenant_id, net_id)
         port = db.port_create(net_id, port_state)
+        # Put operational status UP
+        db.port_update(port.uuid, net_id,
+                       op_status=OperationalStatus.UP)
         port_item = {'port-id': str(port.uuid)}
         return port_item
 
index d021c82f85254a15bd350dc8d4cfd78c424495bf..629c246b44a174a3cd365447368d733b63d8c329 100644 (file)
@@ -157,9 +157,8 @@ class AbstractAPITest(unittest.TestCase):
         self.assertEqual(show_network_res.status_int, 200)
         network_data = self._deserialize_net_response(content_type,
                                                       show_network_res)
-        self.assertEqual({'id': network_id,
-                          'name': self.network_name},
-                         network_data['network'])
+        self.assert_network(id=network_id, name=self.network_name,
+                            network_data=network_data['network'])
         LOG.debug("_test_show_network - fmt:%s - END", fmt)
 
     def _test_show_network_detail(self, fmt):
@@ -174,11 +173,9 @@ class AbstractAPITest(unittest.TestCase):
         self.assertEqual(show_network_res.status_int, 200)
         network_data = self._deserialize_net_response(content_type,
                                                       show_network_res)
-        self.assertEqual({'id': network_id,
-                          'name': self.network_name,
-                          'ports': [{'id': port_id,
-                                     'state': 'ACTIVE'}]},
-                         network_data['network'])
+        self.assert_network_details(id=network_id, name=self.network_name,
+                                    port_id=port_id, port_state='ACTIVE',
+                                    network_data=network_data['network'])
         LOG.debug("_test_show_network_detail - fmt:%s - END", fmt)
 
     def _test_show_network_not_found(self, fmt):
@@ -208,9 +205,8 @@ class AbstractAPITest(unittest.TestCase):
         self.assertEqual(show_network_res.status_int, 200)
         network_data = self._deserialize_net_response(content_type,
                                                       show_network_res)
-        self.assertEqual({'id': network_id,
-                          'name': new_name},
-                         network_data['network'])
+        self.assert_network(id=network_id, name=new_name,
+                            network_data=network_data['network'])
         LOG.debug("_test_rename_network - fmt:%s - END", fmt)
 
     def _test_rename_network_badrequest(self, fmt):
@@ -365,8 +361,8 @@ class AbstractAPITest(unittest.TestCase):
         self.assertEqual(show_port_res.status_int, 200)
         port_data = self._deserialize_port_response(content_type,
                                                     show_port_res)
-        self.assertEqual({'id': port_id, 'state': port_state},
-                         port_data['port'])
+        self.assert_port(id=port_id, state=port_state,
+                        port_data=port_data['port'])
         LOG.debug("_test_show_port - fmt:%s - END", fmt)
 
     def _test_show_port_detail(self, fmt):
@@ -383,8 +379,8 @@ class AbstractAPITest(unittest.TestCase):
         self.assertEqual(show_port_res.status_int, 200)
         port_data = self._deserialize_port_response(content_type,
                                                     show_port_res)
-        self.assertEqual({'id': port_id, 'state': port_state},
-                         port_data['port'])
+        self.assert_port(id=port_id, state=port_state,
+                        port_data=port_data['port'])
 
         # Part 2 - plug attachment into port
         interface_id = "test_interface"
@@ -401,9 +397,9 @@ class AbstractAPITest(unittest.TestCase):
         self.assertEqual(show_port_res.status_int, 200)
         port_data = self._deserialize_port_response(content_type,
                                                     show_port_res)
-        self.assertEqual({'id': port_id, 'state': port_state,
-                          'attachment': {'id': interface_id}},
-                         port_data['port'])
+        self.assert_port_attachment(id=port_id, state=port_state,
+                                    interface_id=interface_id,
+                                    port_data=port_data['port'])
 
         LOG.debug("_test_show_port_detail - fmt:%s - END", fmt)
 
@@ -575,8 +571,8 @@ class AbstractAPITest(unittest.TestCase):
         self.assertEqual(show_port_res.status_int, 200)
         port_data = self._deserialize_port_response(content_type,
                                                     show_port_res)
-        self.assertEqual({'id': port_id, 'state': new_port_state},
-                         port_data['port'])
+        self.assert_port(id=port_id, state=new_port_state,
+                         port_data=port_data['port'])
         # now set it back to the original value
         update_port_req = testlib.update_port_request(self.tenant_id,
                                                         network_id, port_id,
@@ -591,8 +587,8 @@ class AbstractAPITest(unittest.TestCase):
         self.assertEqual(show_port_res.status_int, 200)
         port_data = self._deserialize_port_response(content_type,
                                                     show_port_res)
-        self.assertEqual({'id': port_id, 'state': port_state},
-                         port_data['port'])
+        self.assert_port(id=port_id, state=port_state,
+                         port_data=port_data['port'])
         LOG.debug("_test_set_port_state - fmt:%s - END", fmt)
 
     def _test_set_port_state_networknotfound(self, fmt):
index 5d637c9e540a4b77db78b6a302ec900ffa9172c2..57c5b4cb44642c758d2cfb85281d964db333dfd9 100644 (file)
@@ -21,9 +21,33 @@ import quantum.api.networks as nets
 import quantum.api.ports as ports
 import quantum.tests.unit._test_api as test_api
 
+from quantum.common.test_lib import test_config
+
 
 class APITestV10(test_api.AbstractAPITest):
 
+    def assert_network(self, **kwargs):
+        self.assertEqual({'id': kwargs['id'],
+                          'name': kwargs['name']},
+                          kwargs['network_data'])
+
+    def assert_network_details(self, **kwargs):
+        self.assertEqual({'id': kwargs['id'],
+                          'name': kwargs['name'],
+                          'ports': [{'id': kwargs['port_id'],
+                                     'state': 'ACTIVE'}]},
+                         kwargs['network_data'])
+
+    def assert_port(self, **kwargs):
+        self.assertEqual({'id': kwargs['id'],
+                          'state': kwargs['state']},
+                         kwargs['port_data'])
+
+    def assert_port_attachment(self, **kwargs):
+        self.assertEqual({'id': kwargs['id'], 'state': kwargs['state'],
+                          'attachment': {'id': kwargs['interface_id']}},
+                         kwargs['port_data'])
+
     def setUp(self):
         super(APITestV10, self).setUp('quantum.api.APIRouterV10',
              {test_api.NETS: nets.ControllerV10._serialization_metadata,
@@ -33,7 +57,38 @@ class APITestV10(test_api.AbstractAPITest):
 
 class APITestV11(test_api.AbstractAPITest):
 
+    def assert_network(self, **kwargs):
+        self.assertEqual({'id': kwargs['id'],
+                          'name': kwargs['name'],
+                          'op-status': self.net_op_status},
+                          kwargs['network_data'])
+
+    def assert_network_details(self, **kwargs):
+        self.assertEqual({'id': kwargs['id'],
+                          'name': kwargs['name'],
+                          'op-status': self.net_op_status,
+                          'ports': [{'id': kwargs['port_id'],
+                                     'state': 'ACTIVE',
+                                     'op-status': self.port_op_status}]},
+                         kwargs['network_data'])
+
+    def assert_port(self, **kwargs):
+        self.assertEqual({'id': kwargs['id'],
+                          'state': kwargs['state'],
+                          'op-status': self.port_op_status},
+                         kwargs['port_data'])
+
+    def assert_port_attachment(self, **kwargs):
+        self.assertEqual({'id': kwargs['id'], 'state': kwargs['state'],
+                          'op-status': self.port_op_status,
+                          'attachment': {'id': kwargs['interface_id']}},
+                         kwargs['port_data'])
+
     def setUp(self):
+        self.net_op_status = test_config.get('default_net_op_status',
+                                             'UNKNOWN')
+        self.port_op_status = test_config.get('default_port_op_status',
+                                              'UNKNOWN')
         super(APITestV11, self).setUp('quantum.api.APIRouterV11',
              {test_api.NETS: nets.ControllerV11._serialization_metadata,
               test_api.PORTS: ports.ControllerV11._serialization_metadata,