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:
parent
2ccf22e98a
commit
6e38dcb70e
@ -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
|
||||
|
@ -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"}
|
||||
|
@ -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"}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user