From 37d42caeab3a9e040291afecb5238b28028f566e Mon Sep 17 00:00:00 2001 From: Chris Yeoh Date: Mon, 17 Dec 2012 10:40:06 +1030 Subject: [PATCH] Add more association support to network API Adds API support to: - Disassociate only host from network - Disassociate only project from network - Associate network with host (project already supported) New functionality is added to a new extension networks_associate, but the original networks extension is modified to allow networks_associate to extend how it needs to. The original behavior of the networks extension is preserved (no new functionality, nor change in existing behavior) in terms of the API presented. Bumps RPC API version for network RPC server version in order add the associate function which allows to both associate and disassociate a network with a host and or tenant. Has overlap with disassociate and add_network_to_project but these are kept for backwards compatibility DocImpact Implmenents: blueprint apis-for-nova-manage Change-Id: I78fd76e0696d1c872646355ab04d32f051551def --- .../all_extensions/extensions-get-resp.json | 8 +++ .../all_extensions/extensions-get-resp.xml | 3 + .../network-associate-host-req.json | 3 + .../network-associate-host-req.xml | 2 + .../network-disassociate-host-req.json | 3 + .../network-disassociate-host-req.xml | 1 + .../network-disassociate-project-req.json | 3 + .../network-disassociate-project-req.xml | 1 + .../network-disassociate-req.json | 3 + .../network-disassociate-req.xml | 1 + etc/nova/policy.json | 1 + .../api/openstack/compute/contrib/networks.py | 40 ++++------- .../compute/contrib/networks_associate.py | 69 +++++++++++++++++++ nova/db/api.py | 9 ++- nova/db/sqlalchemy/api.py | 11 ++- nova/network/api.py | 12 ++++ nova/network/manager.py | 23 ++++++- nova/network/rpcapi.py | 6 ++ .../compute/contrib/test_networks.py | 55 ++++++++++++++- nova/tests/fake_policy.py | 1 + .../extensions-get-resp.json.tpl | 8 +++ .../extensions-get-resp.xml.tpl | 3 + .../network-associate-host-req.json.tpl | 3 + .../network-associate-host-req.xml.tpl | 2 + .../network-disassociate-host-req.json.tpl | 3 + .../network-disassociate-host-req.xml.tpl | 1 + .../network-disassociate-project-req.json.tpl | 3 + .../network-disassociate-project-req.xml.tpl | 1 + .../network-disassociate-req.json.tpl | 3 + .../network-disassociate-req.xml.tpl | 1 + nova/tests/integrated/test_api_samples.py | 54 +++++++++++++++ nova/tests/network/test_rpcapi.py | 7 ++ 32 files changed, 310 insertions(+), 34 deletions(-) create mode 100644 doc/api_samples/os-networks-associate/network-associate-host-req.json create mode 100644 doc/api_samples/os-networks-associate/network-associate-host-req.xml create mode 100644 doc/api_samples/os-networks-associate/network-disassociate-host-req.json create mode 100644 doc/api_samples/os-networks-associate/network-disassociate-host-req.xml create mode 100644 doc/api_samples/os-networks-associate/network-disassociate-project-req.json create mode 100644 doc/api_samples/os-networks-associate/network-disassociate-project-req.xml create mode 100644 doc/api_samples/os-networks-associate/network-disassociate-req.json create mode 100644 doc/api_samples/os-networks-associate/network-disassociate-req.xml create mode 100644 nova/api/openstack/compute/contrib/networks_associate.py create mode 100644 nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.xml.tpl diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json index 79211b946b65..b85fae2dea9a 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.json +++ b/doc/api_samples/all_extensions/extensions-get-resp.json @@ -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", diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml index 6c53c875b16a..049498fc43be 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.xml +++ b/doc/api_samples/all_extensions/extensions-get-resp.xml @@ -125,6 +125,9 @@ Admin-only Network Management Extension + + Network association support + Quota classes management support diff --git a/doc/api_samples/os-networks-associate/network-associate-host-req.json b/doc/api_samples/os-networks-associate/network-associate-host-req.json new file mode 100644 index 000000000000..a6487211ee5d --- /dev/null +++ b/doc/api_samples/os-networks-associate/network-associate-host-req.json @@ -0,0 +1,3 @@ +{ + "associate_host": "testHost" +} \ No newline at end of file diff --git a/doc/api_samples/os-networks-associate/network-associate-host-req.xml b/doc/api_samples/os-networks-associate/network-associate-host-req.xml new file mode 100644 index 000000000000..3221be61d4a1 --- /dev/null +++ b/doc/api_samples/os-networks-associate/network-associate-host-req.xml @@ -0,0 +1,2 @@ + +testHost \ No newline at end of file diff --git a/doc/api_samples/os-networks-associate/network-disassociate-host-req.json b/doc/api_samples/os-networks-associate/network-disassociate-host-req.json new file mode 100644 index 000000000000..d6c5419fd17a --- /dev/null +++ b/doc/api_samples/os-networks-associate/network-disassociate-host-req.json @@ -0,0 +1,3 @@ +{ + "disassociate_host": null +} \ No newline at end of file diff --git a/doc/api_samples/os-networks-associate/network-disassociate-host-req.xml b/doc/api_samples/os-networks-associate/network-disassociate-host-req.xml new file mode 100644 index 000000000000..3c2cc0d843ef --- /dev/null +++ b/doc/api_samples/os-networks-associate/network-disassociate-host-req.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/api_samples/os-networks-associate/network-disassociate-project-req.json b/doc/api_samples/os-networks-associate/network-disassociate-project-req.json new file mode 100644 index 000000000000..6c0e46730130 --- /dev/null +++ b/doc/api_samples/os-networks-associate/network-disassociate-project-req.json @@ -0,0 +1,3 @@ +{ + "disassociate_project": null +} \ No newline at end of file diff --git a/doc/api_samples/os-networks-associate/network-disassociate-project-req.xml b/doc/api_samples/os-networks-associate/network-disassociate-project-req.xml new file mode 100644 index 000000000000..be94feb9f2bc --- /dev/null +++ b/doc/api_samples/os-networks-associate/network-disassociate-project-req.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/api_samples/os-networks-associate/network-disassociate-req.json b/doc/api_samples/os-networks-associate/network-disassociate-req.json new file mode 100644 index 000000000000..66ab7cef0417 --- /dev/null +++ b/doc/api_samples/os-networks-associate/network-disassociate-req.json @@ -0,0 +1,3 @@ +{ + "disassociate": null +} \ No newline at end of file diff --git a/doc/api_samples/os-networks-associate/network-disassociate-req.xml b/doc/api_samples/os-networks-associate/network-disassociate-req.xml new file mode 100644 index 000000000000..bcad8e0a80bd --- /dev/null +++ b/doc/api_samples/os-networks-associate/network-disassociate-req.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index e5eb92c7cb2a..d06430129520 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -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": "", diff --git a/nova/api/openstack/compute/contrib/networks.py b/nova/api/openstack/compute/contrib/networks.py index 0a494ea88ab3..a45de72fe749 100644 --- a/nova/api/openstack/compute/contrib/networks.py +++ b/nova/api/openstack/compute/contrib/networks.py @@ -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) diff --git a/nova/api/openstack/compute/contrib/networks_associate.py b/nova/api/openstack/compute/contrib/networks_associate.py new file mode 100644 index 000000000000..a923c769db60 --- /dev/null +++ b/nova/api/openstack/compute/contrib/networks_associate.py @@ -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] diff --git a/nova/db/api.py b/nova/db/api.py index 67d8e7618c0b..4acff8a99ba0 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -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"): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 29c40bb698d0..ec85ddcef65f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -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 diff --git a/nova/network/api.py b/nova/network/api.py index db8b872680e6..beee802c1845 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -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.""" diff --git a/nova/network/manager.py b/nova/network/manager.py index d916d3fb74da..cea7f6dc304d 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -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 diff --git a/nova/network/rpcapi.py b/nova/network/rpcapi.py index f7bc02d84222..8ee1ce4431b3 100644 --- a/nova/network/rpcapi.py +++ b/nova/network/rpcapi.py @@ -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', diff --git a/nova/tests/api/openstack/compute/contrib/test_networks.py b/nova/tests/api/openstack/compute/contrib/test_networks.py index a50e8d89e3b4..367e61910afb 100644 --- a/nova/tests/api/openstack/compute/contrib/test_networks.py +++ b/nova/tests/api/openstack/compute/contrib/test_networks.py @@ -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) diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 3823a77b0378..9c11409226d3 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -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": "", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index efd6893bb0a7..4a8d96844a60 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -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", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index ee957be90158..7d4683986384 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -114,6 +114,9 @@ %(text)s + + %(text)s + %(text)s diff --git a/nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.json.tpl b/nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.json.tpl new file mode 100644 index 000000000000..762e8817518e --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.json.tpl @@ -0,0 +1,3 @@ +{ + "associate_host": "%(host)s" +} diff --git a/nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.xml.tpl b/nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.xml.tpl new file mode 100644 index 000000000000..7c96c96a12d6 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.xml.tpl @@ -0,0 +1,2 @@ + +%(host)s diff --git a/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.json.tpl b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.json.tpl new file mode 100644 index 000000000000..46f69b3e8190 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.json.tpl @@ -0,0 +1,3 @@ +{ + "disassociate_host": null +} diff --git a/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.xml.tpl b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.xml.tpl new file mode 100644 index 000000000000..910504a44ab3 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.xml.tpl @@ -0,0 +1 @@ + diff --git a/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.json.tpl b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.json.tpl new file mode 100644 index 000000000000..63b6eb683997 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.json.tpl @@ -0,0 +1,3 @@ +{ + "disassociate_project": null +} diff --git a/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.xml.tpl b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.xml.tpl new file mode 100644 index 000000000000..d4162c19e0e9 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.xml.tpl @@ -0,0 +1 @@ + diff --git a/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.json.tpl b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.json.tpl new file mode 100644 index 000000000000..2e09d15a60a9 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.json.tpl @@ -0,0 +1,3 @@ +{ + "disassociate": null +} diff --git a/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.xml.tpl b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.xml.tpl new file mode 100644 index 000000000000..c26f7b61a829 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.xml.tpl @@ -0,0 +1 @@ + diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index 8b78d3b51b44..49ff77306032 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -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' diff --git a/nova/tests/network/test_rpcapi.py b/nova/tests/network/test_rpcapi.py index dd6cccf0f61e..2ded5230db6d 100644 --- a/nova/tests/network/test_rpcapi.py +++ b/nova/tests/network/test_rpcapi.py @@ -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')