blueprint api-operational-status

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
This commit is contained in:
Salvatore Orlando 2011-12-05 17:28:33 +00:00
parent 2ccf22e98a
commit 6e38dcb70e
12 changed files with 190 additions and 49 deletions

View 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

View 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"}

View 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"}
}

View 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

View File

@ -15,13 +15,19 @@
# 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

View 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",
}

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

View File

@ -16,14 +16,15 @@
# @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)

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

View 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

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

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