Adds tenant network support to the client

Modifies novaclient, changing the existing networks implementation to a
more appropriate os-admin-networks namespace, and supplements with a
tenant-base network extenion that lives under the os-networks namespace
as a Nova API extension. Also removes from the duplicately named network
test methods.

Implements: blueprint tenant-networks

Change-Id: I54c9f017b86fc413f1646c7bded8cebd94f6a287
This commit is contained in:
Matt Dietz 2012-12-18 15:03:55 +00:00
parent 7b124b8872
commit dffd415fba
7 changed files with 196 additions and 36 deletions

View File

@ -0,0 +1,77 @@
# Copyright 2013 OpenStack, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from novaclient import base
from novaclient import utils
class TenantNetwork(base.Resource):
def delete(self):
self.manager.delete(network=self)
class TenantNetworkManager(base.ManagerWithFind):
resource_class = base.Resource
def list(self):
return self._list('/os-tenant-networks', 'networks')
def get(self, network):
return self._get('/os-tenant-networks/%s' % base.getid(network),
'network')
def delete(self, network):
self._delete('/os-tenant-networks/%s' % base.getid(network))
def create(self, label, cidr):
body = {'network': {'label': label, 'cidr': cidr}}
return self._create('/os-tenant-networks', body, 'network')
@utils.arg('network_id', metavar='<network_id>', help='ID of network')
def do_net(cs, args):
"""
Show a network
"""
network = cs.tenant_networks.get(args.network_id)
utils.print_dict(network._info)
def do_net_list(cs, args):
"""
List networks
"""
networks = cs.tenant_networks.list()
utils.print_list(networks, ['ID', 'Label', 'CIDR'])
@utils.arg('label', metavar='<network_label>',
help='Network label (ex. my_new_network)')
@utils.arg('cidr', metavar='<cidr>',
help='IP block to allocate from (ex. 172.16.0.0/24 or '
'2001:DB8::/64)')
def do_net_create(cs, args):
"""
Create a network
"""
network = cs.tenant_networks.create(args.label, args.cidr)
utils.print_dict(network._info)
@utils.arg('network_id', metavar='<network_id>', help='ID of network')
def do_net_delete(cs, args):
"""
Delete a network
"""
cs.tenant_networks.delete(args.network_id)

View File

@ -18,6 +18,7 @@ Network interface.
"""
from novaclient import base
from novaclient import exceptions
class Network(base.Resource):
@ -55,7 +56,8 @@ class NetworkManager(base.ManagerWithFind):
:param network: The ID of the :class:`Network` to get.
:rtype: :class:`Network`
"""
return self._get("/os-networks/%s" % base.getid(network), "network")
return self._get("/os-networks/%s" % base.getid(network),
"network")
def delete(self, network):
"""
@ -107,11 +109,11 @@ class NetworkManager(base.ManagerWithFind):
elif disassociate_host:
body = {"disassociate_host": None}
else:
raise CommandError(
raise exceptions.CommandError(
"Must disassociate either host or project or both")
self.api.client.post("/os-networks/%s/action" % base.getid(network),
body=body)
self.api.client.post("/os-networks/%s/action" %
base.getid(network), body=body)
def associate_host(self, network, host):
"""
@ -120,7 +122,8 @@ class NetworkManager(base.ManagerWithFind):
:param network: The ID of the :class:`Network`.
:param host: The name of the host to associate the network with
"""
self.api.client.post("/os-networks/%s/action" % base.getid(network),
self.api.client.post("/os-networks/%s/action" %
base.getid(network),
body={"associate_host": host})
def associate_project(self, network):

View File

@ -0,0 +1,44 @@
# Copyright 2012 OpenStack, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from novaclient.v1_1 import client
from tests.v1_1 import fakes
class FakeClient(fakes.FakeClient):
def __init__(self, *args, **kwargs):
client.Client.__init__(self, 'username', 'password',
'project_id', 'auth_url',
extensions=kwargs.get('extensions'))
self.client = FakeHTTPClient(**kwargs)
class FakeHTTPClient(fakes.FakeHTTPClient):
def get_os_tenant_networks(self):
return (200, {}, {'networks': [{"label": "1", "cidr": "10.0.0.0/24",
'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
'id': '1'}]})
def get_os_tenant_networks_1(self, **kw):
return (200, {}, {'network': {"label": "1", "cidr": "10.0.0.0/24",
'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
'id': '1'}})
def post_os_tenant_networks(self, **kw):
return (201, {}, {'network': {"label": "1", "cidr": "10.0.0.0/24",
'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
'id': '1'}})
def delete_os_tenant_networks_1(self, **kw):
return (204, {}, None)

View File

@ -0,0 +1,50 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from novaclient import extension
from novaclient.v1_1.contrib import tenant_networks
from tests import utils
from tests.v1_1.contrib import fakes
extensions = [
extension.Extension(tenant_networks.__name__.split(".")[-1],
tenant_networks),
]
cs = fakes.FakeClient(extensions=extensions)
class TenantNetworkExtensionTests(utils.TestCase):
def test_list_tenant_networks(self):
nets = cs.tenant_networks.list()
cs.assert_called('GET', '/os-tenant-networks')
self.assertTrue(len(nets) > 0)
def test_get_tenant_network(self):
net = cs.tenant_networks.get(1)
cs.assert_called('GET', '/os-tenant-networks/1')
print net
def test_create_tenant_networks(self):
cs.tenant_networks.create(label="net",
cidr="10.0.0.0/24")
cs.assert_called('POST', '/os-tenant-networks')
def test_delete_tenant_networks(self):
cs.tenant_networks.delete(1)
cs.assert_called('DELETE', '/os-tenant-networks/1')

View File

@ -16,8 +16,6 @@
from datetime import datetime
import urlparse
import requests
from novaclient import client as base_client
from novaclient.v1_1 import client
from tests import fakes
@ -65,10 +63,6 @@ class FakeHTTPClient(base_client.HTTPClient):
# Note the call
self.callstack.append((method, url, kwargs.get('body', None)))
if 'body' in kwargs:
b = kwargs['body']
else:
b = ''
status, headers, body = getattr(self, callback)(**kwargs)
r = utils.TestResponse({
"status_code": status,
@ -656,9 +650,6 @@ class FakeHTTPClient(base_client.HTTPClient):
{'id': 1, 'fixed_ip': '10.0.0.1', 'ip': '11.0.0.1'}
})
def post_os_floating_ips(self, body, **kw):
return (202, {}, self.get_os_floating_ips_1()[1])
def post_os_floating_ips(self, body):
if body.get('pool'):
return (200, {}, {'floating_ip':
@ -1289,14 +1280,11 @@ class FakeHTTPClient(base_client.HTTPClient):
'project_id': '4ffc664c198e435e9853f2538fbcd7a7',
'id': '1'}]})
def get_os_networks_1(self, **kw):
return (200, {}, {'network': {"label": "1", "cidr": "10.0.0.0/24"}})
def post_os_networks(self, **kw):
return (202, {}, {'network': kw})
def post_os_networks_1_action(self, **kw):
return (202, {}, None)
def get_os_networks_1(self, **kw):
return (200, {}, {'network': {"label": "1", "cidr": "10.0.0.0/24"}})
def delete_os_networks_networkdelete(self, **kw):
return (202, {}, None)
@ -1336,8 +1324,13 @@ class FakeHTTPClient(base_client.HTTPClient):
}
)
def post_os_networks(self, **kw):
return (202, {}, {'network': kw})
def post_os_coverage_action(self, body, **kw):
if 'report' not in body:
return (200, {}, None)
else:
return (200, {}, {
'path': '/tmp/tmpdir/' + body['report']['file']
})
def post_os_networks_1_action(self, **kw):
return (202, {}, None)

View File

@ -31,7 +31,8 @@ class NetworksTest(utils.TestCase):
def test_associate_project(self):
cs.networks.associate_project('networktest')
cs.assert_called('POST', '/os-networks/add', {'id': 'networktest'})
cs.assert_called('POST', '/os-networks/add',
{'id': 'networktest'})
def test_associate_host(self):
cs.networks.associate_host('networktest', 'testHost')
@ -40,17 +41,20 @@ class NetworksTest(utils.TestCase):
def test_disassociate(self):
cs.networks.disassociate('networkdisassociate')
cs.assert_called('POST', '/os-networks/networkdisassociate/action',
cs.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate': None})
def test_disassociate_host_only(self):
cs.networks.disassociate('networkdisassociate', True, False)
cs.assert_called('POST', '/os-networks/networkdisassociate/action',
cs.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate_host': None})
def test_disassociate_project(self):
cs.networks.disassociate('networkdisassociate', False, True)
cs.assert_called('POST', '/os-networks/networkdisassociate/action',
cs.assert_called('POST',
'/os-networks/networkdisassociate/action',
{'disassociate_project': None})
def test_add(self):

View File

@ -738,11 +738,6 @@ class ShellTest(utils.TestCase):
body = {'id': "1"}
self.assert_called('POST', '/os-networks/add', body)
def test_network_disassociate(self):
self.run_command('network-disassociate 1')
body = {'disassociate': None}
self.assert_called('POST', '/os-networks/1/action', body)
def test_network_disassociate_host(self):
self.run_command('network-disassociate --host-only 1 2')
body = {'disassociate_host': None}
@ -753,12 +748,6 @@ class ShellTest(utils.TestCase):
body = {'disassociate_project': None}
self.assert_called('POST', '/os-networks/2/action', body)
def test_network_create_v4(self):
self.run_command('network-create --fixed-range-v4 10.0.1.0/24 \
new_network')
body = {'cidr': '10.0.1.0/24', 'label': 'new_network'}
self.assert_called('POST', '/os-networks', body)
def test_network_create_v4(self):
self.run_command('network-create --fixed-range-v4 10.0.1.0/24 \
--dns1 10.0.1.254 new_network')