summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.json8
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.xml3
-rw-r--r--doc/api_samples/os-networks-associate/network-associate-host-req.json3
-rw-r--r--doc/api_samples/os-networks-associate/network-associate-host-req.xml2
-rw-r--r--doc/api_samples/os-networks-associate/network-disassociate-host-req.json3
-rw-r--r--doc/api_samples/os-networks-associate/network-disassociate-host-req.xml1
-rw-r--r--doc/api_samples/os-networks-associate/network-disassociate-project-req.json3
-rw-r--r--doc/api_samples/os-networks-associate/network-disassociate-project-req.xml1
-rw-r--r--doc/api_samples/os-networks-associate/network-disassociate-req.json3
-rw-r--r--doc/api_samples/os-networks-associate/network-disassociate-req.xml1
-rw-r--r--etc/nova/policy.json1
-rw-r--r--nova/api/openstack/compute/contrib/networks.py40
-rw-r--r--nova/api/openstack/compute/contrib/networks_associate.py69
-rw-r--r--nova/db/api.py9
-rw-r--r--nova/db/sqlalchemy/api.py11
-rw-r--r--nova/network/api.py12
-rw-r--r--nova/network/manager.py23
-rw-r--r--nova/network/rpcapi.py6
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_networks.py55
-rw-r--r--nova/tests/fake_policy.py1
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl8
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl3
-rw-r--r--nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.json.tpl3
-rw-r--r--nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.xml.tpl2
-rw-r--r--nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.json.tpl3
-rw-r--r--nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.xml.tpl1
-rw-r--r--nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.json.tpl3
-rw-r--r--nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.xml.tpl1
-rw-r--r--nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.json.tpl3
-rw-r--r--nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.xml.tpl1
-rw-r--r--nova/tests/integrated/test_api_samples.py54
-rw-r--r--nova/tests/network/test_rpcapi.py7
32 files changed, 310 insertions, 34 deletions
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json
index 79211b946..b85fae2de 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.json
+++ b/doc/api_samples/all_extensions/extensions-get-resp.json
@@ -297,6 +297,14 @@
"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",
"links": [],
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml
index 6c53c875b..049498fc4 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 @@
<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>
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 000000000..a6487211e
--- /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 000000000..3221be61d
--- /dev/null
+++ b/doc/api_samples/os-networks-associate/network-associate-host-req.xml
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<associate_host>testHost</associate_host> \ 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 000000000..d6c5419fd
--- /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 000000000..3c2cc0d84
--- /dev/null
+++ b/doc/api_samples/os-networks-associate/network-disassociate-host-req.xml
@@ -0,0 +1 @@
+<disassociate_host/> \ 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 000000000..6c0e46730
--- /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 000000000..be94feb9f
--- /dev/null
+++ b/doc/api_samples/os-networks-associate/network-disassociate-project-req.xml
@@ -0,0 +1 @@
+<disassociate_project/> \ 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 000000000..66ab7cef0
--- /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 000000000..bcad8e0a8
--- /dev/null
+++ b/doc/api_samples/os-networks-associate/network-disassociate-req.xml
@@ -0,0 +1 @@
+<disassociate/> \ No newline at end of file
diff --git a/etc/nova/policy.json b/etc/nova/policy.json
index e5eb92c7c..d06430129 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 0a494ea88..a45de72fe 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 000000000..a923c769d
--- /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 67d8e7618..4acff8a99 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 29c40bb69..ec85ddcef 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 db8b87268..beee802c1 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 d916d3fb7..cea7f6dc3 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 f7bc02d84..8ee1ce443 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 a50e8d89e..367e61910 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 3823a77b0..9c1140922 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 efd6893bb..4a8d96844 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
@@ -305,6 +305,14 @@
"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",
"links": [],
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 ee957be90..7d4683986 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 @@
<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>
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 000000000..762e88175
--- /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 000000000..7c96c96a1
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-networks-associate/network-associate-host-req.xml.tpl
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<associate_host>%(host)s</associate_host>
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 000000000..46f69b3e8
--- /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 000000000..910504a44
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-host-req.xml.tpl
@@ -0,0 +1 @@
+<disassociate_host/>
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 000000000..63b6eb683
--- /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 000000000..d4162c19e
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-project-req.xml.tpl
@@ -0,0 +1 @@
+<disassociate_project/>
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 000000000..2e09d15a6
--- /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 000000000..c26f7b61a
--- /dev/null
+++ b/nova/tests/integrated/api_samples/os-networks-associate/network-disassociate-req.xml.tpl
@@ -0,0 +1 @@
+<disassociate/>
diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py
index 8b78d3b51..49ff77306 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 dd6cccf0f..2ded5230d 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')