Merge "Add more association support to network API"

This commit is contained in:
Jenkins 2012-12-18 00:23:46 +00:00 committed by Gerrit Code Review
commit 38fd49bb30
32 changed files with 310 additions and 34 deletions

View File

@ -296,6 +296,14 @@
"namespace": "http://docs.openstack.org/compute/ext/networks/api/v1.1",
"updated": "2011-12-23T00:00:00+00:00"
},
{
"alias": "os-networks-associate",
"description": "Network association support",
"links": [],
"name": "NetworkAssociationSupport",
"namespace": "http://docs.openstack.org/compute/ext/networks_associate/api/v2",
"updated": "2012-11-19T00:00:00+00:00"
},
{
"alias": "os-quota-class-sets",
"description": "Quota classes management support",

View File

@ -125,6 +125,9 @@
<extension alias="os-networks" updated="2011-12-23T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/networks/api/v1.1" name="Networks">
<description>Admin-only Network Management Extension</description>
</extension>
<extension alias="os-networks-associate" updated="2012-11-19T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/networks_associate/api/v2" name="NetworkAssociationSupport">
<description>Network association support</description>
</extension>
<extension alias="os-quota-class-sets" updated="2012-03-12T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/quota-classes-sets/api/v1.1" name="QuotaClasses">
<description>Quota classes management support</description>
</extension>

View File

@ -0,0 +1,3 @@
{
"associate_host": "testHost"
}

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<associate_host>testHost</associate_host>

View File

@ -0,0 +1,3 @@
{
"disassociate_host": null
}

View File

@ -0,0 +1 @@
<disassociate_host/>

View File

@ -0,0 +1,3 @@
{
"disassociate_project": null
}

View File

@ -0,0 +1 @@
<disassociate_project/>

View File

@ -0,0 +1,3 @@
{
"disassociate": null
}

View File

@ -0,0 +1 @@
<disassociate/>

View File

@ -61,6 +61,7 @@
"compute_extension:multinic": "",
"compute_extension:networks": "rule:admin_api",
"compute_extension:networks:view": "",
"compute_extension:networks_associate": "rule:admin_api",
"compute_extension:quotas:show": "",
"compute_extension:quotas:update": "rule:admin_api",
"compute_extension:quota_classes": "",

View File

@ -21,6 +21,8 @@ import webob
from webob import exc
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova import db
from nova import exception
from nova import network
from nova.openstack.common import log as logging
@ -52,35 +54,11 @@ def network_dict(context, network):
return {}
class NetworkController(object):
class NetworkController(wsgi.Controller):
def __init__(self, network_api=None):
self.network_api = network_api or network.API()
def action(self, req, id, body):
_actions = {
'disassociate': self._disassociate,
}
for action, data in body.iteritems():
try:
return _actions[action](req, id, body)
except KeyError:
msg = _("Network does not have %s action") % action
raise exc.HTTPBadRequest(explanation=msg)
raise exc.HTTPBadRequest(explanation=_("Invalid request body"))
def _disassociate(self, request, network_id, body):
context = request.environ['nova.context']
authorize(context)
LOG.debug(_("Disassociating network with id %s"), network_id)
try:
self.network_api.disassociate(context, network_id)
except exception.NetworkNotFound:
raise exc.HTTPNotFound(_("Network not found"))
return exc.HTTPAccepted()
def index(self, req):
context = req.environ['nova.context']
authorize_view(context)
@ -88,6 +66,18 @@ class NetworkController(object):
result = [network_dict(context, net_ref) for net_ref in networks]
return {'networks': result}
@wsgi.action("disassociate")
def _disassociate_host_and_project(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
LOG.debug(_("Disassociating network with id %s"), id)
try:
self.network_api.associate(context, id, host=None, project=None)
except exception.NetworkNotFound:
raise exc.HTTPNotFound(_("Network not found"))
return exc.HTTPAccepted()
def show(self, req, id):
context = req.environ['nova.context']
authorize_view(context)

View File

@ -0,0 +1,69 @@
import netaddr
import webob
from webob import exc
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova import exception
from nova import network
from nova.openstack.common import log as logging
LOG = logging.getLogger(__name__)
authorize = extensions.extension_authorizer('compute', 'networks_associate')
class NetworkAssociateActionController(wsgi.Controller):
"""Network Association API Controller."""
def __init__(self, network_api=None):
self.network_api = network_api or network.API()
@wsgi.action("disassociate_host")
def _disassociate_host_only(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
LOG.debug(_("Disassociating host with network with id %s"), id)
try:
self.network_api.associate(context, id, host=None)
except exception.NetworkNotFound:
raise exc.HTTPNotFound(_("Network not found"))
return exc.HTTPAccepted()
@wsgi.action("disassociate_project")
def _disassociate_project_only(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
LOG.debug(_("Disassociating project with network with id %s"), id)
try:
self.network_api.associate(context, id, project=None)
except exception.NetworkNotFound:
raise exc.HTTPNotFound(_("Network not found"))
return exc.HTTPAccepted()
@wsgi.action("associate_host")
def _associate_host(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
try:
self.network_api.associate(context, id,
host=body['associate_host'])
except exception.NetworkNotFound:
raise exc.HTTPNotFound(_("Network not found"))
return exc.HTTPAccepted()
class Networks_associate(extensions.ExtensionDescriptor):
"""Network association support"""
name = "NetworkAssociationSupport"
alias = "os-networks-associate"
namespace = ("http://docs.openstack.org/compute/ext/"
"networks_associate/api/v2")
updated = "2012-11-19T00:00:00+00:00"
def get_controller_extensions(self):
extension = extensions.ControllerExtension(
self, 'os-networks', NetworkAssociateActionController())
return [extension]

View File

@ -785,9 +785,12 @@ def network_delete_safe(context, network_id):
return IMPL.network_delete_safe(context, network_id)
def network_disassociate(context, network_id):
"""Disassociate the network from project or raise if it does not exist."""
return IMPL.network_disassociate(context, network_id)
def network_disassociate(context, network_id, disassociate_host=True,
disassociate_project=True):
"""Disassociate the network from project or host and raise if it does
not exist."""
return IMPL.network_disassociate(context, network_id, disassociate_host,
disassociate_project)
def network_get(context, network_id, project_only="allow_none"):

View File

@ -2135,9 +2135,14 @@ def network_delete_safe(context, network_id):
@require_admin_context
def network_disassociate(context, network_id):
network_update(context, network_id, {'project_id': None,
'host': None})
def network_disassociate(context, network_id, disassociate_host,
disassociate_project):
net_update = {}
if disassociate_project:
net_update['project_id'] = None
if disassociate_host:
net_update['host'] = None
network_update(context, network_id, net_update)
@require_context

View File

@ -82,6 +82,8 @@ def update_instance_cache_with_nw_info(api, context, instance,
class API(base.Base):
"""API for interacting with the network manager."""
_sentinel = object()
def __init__(self, **kwargs):
self.network_rpcapi = network_rpcapi.NetworkAPI()
super(API, self).__init__(**kwargs)
@ -232,6 +234,16 @@ class API(base.Base):
self.network_rpcapi.add_network_to_project(context, project_id,
network_uuid)
def associate(self, context, network_uuid, host=_sentinel,
project=_sentinel):
"""Associate or disassociate host or project to network"""
associations = {}
if host is not API._sentinel:
associations['host'] = host
if project is not API._sentinel:
associations['project'] = project
self.network_rpcapi.associate(context, network_uuid, associations)
@refresh_cache
def get_instance_nw_info(self, context, instance):
"""Returns all network info related to an instance."""

View File

@ -887,7 +887,7 @@ class NetworkManager(manager.SchedulerDependentManager):
The one at a time part is to flatten the layout to help scale
"""
RPC_API_VERSION = '1.4'
RPC_API_VERSION = '1.5'
# If True, this manager requires VIF to create a bridge.
SHOULD_CREATE_BRIDGE = False
@ -2210,6 +2210,27 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
network_id = None
self.db.network_associate(context, project_id, network_id, force=True)
@wrap_check_policy
def associate(self, context, network_uuid, associations):
"""Associate or disassociate host or project to network."""
network_id = self.get_network(context, network_uuid)['id']
if 'host' in associations:
host = associations['host']
if host is None:
self.db.network_disassociate(context, network_id,
disassociate_host=True,
disassociate_project=False)
else:
self.db.network_set_host(context, network_id, host)
if 'project' in associations:
project = associations['project']
if project is None:
self.db.network_disassociate(context, network_id,
disassociate_host=False,
disassociate_project=True)
else:
self.db.network_associate(context, project, network_id, True)
def _get_network_by_id(self, context, network_id):
# NOTE(vish): Don't allow access to networks with project_id=None as
# these are networksa that haven't been allocated to a

View File

@ -37,6 +37,7 @@ class NetworkAPI(rpc_proxy.RpcProxy):
1.2 - Make migrate_instance_[start|finish] a little more flexible
1.3 - Adds fanout cast update_dns for multi_host networks
1.4 - Add get_backdoor_port()
1.5 - Adds associate
'''
#
@ -163,6 +164,11 @@ class NetworkAPI(rpc_proxy.RpcProxy):
return self.call(ctxt, self.make_msg('add_network_to_project',
project_id=project_id, network_uuid=network_uuid))
def associate(self, ctxt, network_uuid, associations):
return self.call(ctxt, self.make_msg('associate',
network_uuid=network_uuid, associations=associations),
self.topic, version="1.5")
def get_instance_nw_info(self, ctxt, instance_id, instance_uuid,
rxtx_factor, host, project_id):
return self.call(ctxt, self.make_msg('get_instance_nw_info',

View File

@ -23,6 +23,9 @@ import uuid
import webob
from nova.api.openstack.compute.contrib import networks
from nova.api.openstack.compute.contrib import networks_associate
from nova import config
from nova import db
from nova import exception
from nova.openstack.common import cfg
from nova import test
@ -93,6 +96,8 @@ NEW_NETWORK = {
class FakeNetworkAPI(object):
_sentinel = object()
def __init__(self):
self.networks = copy.deepcopy(FAKE_NETWORKS)
@ -110,6 +115,17 @@ class FakeNetworkAPI(object):
return True
raise exception.NetworkNotFound()
def associate(self, context, network_uuid, host=_sentinel,
project=_sentinel):
for network in self.networks:
if network.get('uuid') == network_uuid:
if host is not FakeNetworkAPI._sentinel:
network['host'] = host
if project is not FakeNetworkAPI._sentinel:
network['project_id'] = project
return True
raise exception.NetworkNotFound()
def add_network_to_project(self, context,
project_id, network_uuid=None):
if network_uuid:
@ -165,6 +181,8 @@ class NetworksTest(test.TestCase):
super(NetworksTest, self).setUp()
self.fake_network_api = FakeNetworkAPI()
self.controller = networks.NetworkController(self.fake_network_api)
self.associate_controller = networks_associate\
.NetworkAssociateActionController(self.fake_network_api)
fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs)
@ -194,13 +212,35 @@ class NetworksTest(test.TestCase):
def test_network_disassociate(self):
uuid = FAKE_NETWORKS[0]['uuid']
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s/action' % uuid)
res = self.controller.action(req, uuid, {'disassociate': None})
res = self.controller._disassociate_host_and_project(
req, uuid, {'disassociate': None})
self.assertEqual(res.status_int, 202)
self.assertEqual(self.fake_network_api.networks[0]['project_id'], None)
self.assertEqual(self.fake_network_api.networks[0]['host'], None)
def test_network_disassociate_host_only(self):
uuid = FAKE_NETWORKS[0]['uuid']
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s/action' % uuid)
res = self.associate_controller._disassociate_host_only(
req, uuid, {'disassociate_host': None})
self.assertEqual(res.status_int, 202)
self.assertNotEqual(self.fake_network_api.networks[0]['project_id'],
None)
self.assertEqual(self.fake_network_api.networks[0]['host'], None)
def test_network_disassociate_project_only(self):
uuid = FAKE_NETWORKS[0]['uuid']
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s/action' % uuid)
res = self.associate_controller._disassociate_project_only(
req, uuid, {'disassociate_project': None})
self.assertEqual(res.status_int, 202)
self.assertEqual(self.fake_network_api.networks[0]['project_id'], None)
self.assertNotEqual(self.fake_network_api.networks[0]['host'], None)
def test_network_disassociate_not_found(self):
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/100/action')
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.action,
self.controller._disassociate_host_and_project,
req, 100, {'disassociate': None})
def test_network_get_as_user(self):
@ -246,6 +286,17 @@ class NetworksTest(test.TestCase):
res_dict = self.controller.show(req, uuid)
self.assertEqual(res_dict['network']['project_id'], 'fake')
def test_network_associate_with_host(self):
uuid = FAKE_NETWORKS[1]['uuid']
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s/action' % uuid)
res = self.associate_controller._associate_host(
req, uuid, {'associate_host': "TestHost"})
self.assertEqual(res.status_int, 202)
req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s' % uuid)
req.environ["nova.context"].is_admin = True
res_dict = self.controller.show(req, uuid)
self.assertEqual(res_dict['network']['host'], 'TestHost')
def test_network_create(self):
req = fakes.HTTPRequest.blank('/v2/1234/os-networks')
res_dict = self.controller.create(req, NEW_NETWORK)

View File

@ -137,6 +137,7 @@ policy_data = """
"compute_extension:multinic": "",
"compute_extension:networks": "",
"compute_extension:networks:view": "",
"compute_extension:networks_associate": "",
"compute_extension:quotas:show": "",
"compute_extension:quotas:update": "",
"compute_extension:quota_classes": "",

View File

@ -304,6 +304,14 @@
"namespace": "http://docs.openstack.org/compute/ext/networks/api/v1.1",
"updated": "%(timestamp)s"
},
{
"alias": "os-networks-associate",
"description": "%(text)s",
"links": [],
"name": "NetworkAssociationSupport",
"namespace": "http://docs.openstack.org/compute/ext/networks_associate/api/v2",
"updated": "%(timestamp)s"
},
{
"alias": "os-quota-class-sets",
"description": "%(text)s",

View File

@ -114,6 +114,9 @@
<extension alias="os-networks" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/networks/api/v1.1" name="Networks">
<description>%(text)s</description>
</extension>
<extension alias="os-networks-associate" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/networks_associate/api/v2" name="NetworkAssociationSupport">
<description>%(text)s</description>
</extension>
<extension alias="os-quota-class-sets" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/quota-classes-sets/api/v1.1" name="QuotaClasses">
<description>%(text)s</description>
</extension>

View File

@ -0,0 +1,3 @@
{
"associate_host": "%(host)s"
}

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<associate_host>%(host)s</associate_host>

View File

@ -0,0 +1,3 @@
{
"disassociate_host": null
}

View File

@ -0,0 +1,3 @@
{
"disassociate_project": null
}

View File

@ -0,0 +1 @@
<disassociate_project/>

View File

@ -0,0 +1,3 @@
{
"disassociate": null
}

View File

@ -30,6 +30,7 @@ from nova.compute import api
from nova import context
from nova import db
from nova.db.sqlalchemy import models
from nova.network import api
from nova.network.manager import NetworkManager
from nova.openstack.common import cfg
from nova.openstack.common import importutils
@ -2048,3 +2049,56 @@ class DiskConfigJsonTest(ServersSampleBase):
class DiskConfigXmlTest(DiskConfigJsonTest):
ctype = 'xml'
class NetworksAssociateJsonTests(ApiSampleTestBase):
extension_name = ("nova.api.openstack.compute.contrib"
".networks_associate.Networks_associate")
_sentinel = object()
def _get_flags(self):
f = super(NetworksAssociateJsonTests, self)._get_flags()
f['osapi_compute_extension'] = CONF.osapi_compute_extension[:]
# Networks_associate requires Networks to be update
f['osapi_compute_extension'].append(
'nova.api.openstack.compute.contrib.networks.Networks')
return f
def setUp(self):
super(NetworksAssociateJsonTests, self).setUp()
def fake_associate(self, context, network_id,
host=NetworksAssociateJsonTests._sentinel,
project=NetworksAssociateJsonTests._sentinel):
return True
self.stubs.Set(api.API, "associate", fake_associate)
def test_disassociate(self):
response = self._do_post('os-networks/1/action',
'network-disassociate-req',
{})
self.assertEqual(response.status, 202)
def test_disassociate_host(self):
response = self._do_post('os-networks/1/action',
'network-disassociate-host-req',
{})
self.assertEqual(response.status, 202)
def test_disassociate_project(self):
response = self._do_post('os-networks/1/action',
'network-disassociate-project-req',
{})
self.assertEqual(response.status, 202)
def test_associate_host(self):
response = self._do_post('os-networks/1/action',
'network-associate-host-req',
{"host": "testHost"})
self.assertEqual(response.status, 202)
class NetworksAssociateXmlTests(NetworksAssociateJsonTests):
ctype = 'xml'

View File

@ -92,6 +92,13 @@ class NetworkRpcAPITestCase(test.TestCase):
self._test_network_api('disassociate_network', rpc_method='call',
network_uuid='fake_uuid')
def test_associate_host_and_project(self):
self._test_network_api('associate', rpc_method='call',
network_uuid='fake_uuid',
associations={'host': "testHost",
'project': 'testProject'},
version="1.5")
def test_get_fixed_ip(self):
self._test_network_api('get_fixed_ip', rpc_method='call', id='id')