diff options
| -rw-r--r-- | Authors | 1 | ||||
| -rw-r--r-- | nova/network/quantumv2/__init__.py | 58 | ||||
| -rw-r--r-- | nova/network/quantumv2/api.py | 344 | ||||
| -rw-r--r-- | nova/tests/network/test_quantum.py | 4 | ||||
| -rw-r--r-- | nova/tests/network/test_quantumv2.py | 406 | ||||
| -rw-r--r-- | tools/pip-requires | 1 |
6 files changed, 812 insertions, 2 deletions
@@ -224,6 +224,7 @@ William Wolf <throughnothing@gmail.com> Yaguang Tang <heut2008@gmail.com> Ying Chun Guo <daisy.ycguo@gmail.com> Yoshiaki Tamura <yoshi@midokura.jp> +Yong Sheng Gong <gongysh@cn.ibm.com> Youcef Laribi <Youcef.Laribi@eu.citrix.com> Yun Mao <yunmao@gmail.com> Yun Shen <Yun.Shen@hp.com> diff --git a/nova/network/quantumv2/__init__.py b/nova/network/quantumv2/__init__.py new file mode 100644 index 000000000..ff140fd72 --- /dev/null +++ b/nova/network/quantumv2/__init__.py @@ -0,0 +1,58 @@ +# 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. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from nova import exception +from nova import flags +from nova.openstack.common import log as logging +from nova.openstack.common import excutils +from quantumclient import client +from quantumclient.v2_0 import client as clientv20 + +FLAGS = flags.FLAGS +LOG = logging.getLogger(__name__) + + +def _get_auth_token(): + try: + httpclient = client.HTTPClient( + username=FLAGS.quantum_admin_username, + tenant_name=FLAGS.quantum_admin_tenant_name, + password=FLAGS.quantum_admin_password, + auth_url=FLAGS.quantum_admin_auth_url, + timeout=FLAGS.quantum_url_timeout, + auth_strategy=FLAGS.quantum_auth_strategy) + httpclient.authenticate() + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("_get_auth_token() failed")) + return httpclient.auth_token + + +def get_client(context): + token = context.auth_token + if not token: + if FLAGS.quantum_auth_strategy: + token = _get_auth_token() + if token: + my_client = clientv20.Client( + endpoint_url=FLAGS.quantum_url, + token=token, timeout=FLAGS.quantum_url_timeout) + else: + my_client = clientv20.Client( + endpoint_url=FLAGS.quantum_url, + auth_strategy=None, timeout=FLAGS.quantum_url_timeout) + return my_client diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py new file mode 100644 index 000000000..1e796a308 --- /dev/null +++ b/nova/network/quantumv2/api.py @@ -0,0 +1,344 @@ +# 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. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +from nova.db import base +from nova import exception +from nova import flags +from nova.openstack.common import log as logging +from nova.network.api import refresh_cache +from nova.network import model as network_model +from nova.network import quantumv2 +from nova.openstack.common import cfg +from nova.openstack.common import excutils + +quantum_opts = [ + cfg.StrOpt('quantum_url', + default='http://127.0.0.1:9696', + help='URL for connecting to quantum'), + cfg.IntOpt('quantum_url_timeout', + default=30, + help='timeout value for connecting to quantum in seconds'), + cfg.StrOpt('quantum_admin_username', + help='username for connecting to quantum in admin context'), + cfg.StrOpt('quantum_admin_password', + help='password for connecting to quantum in admin context'), + cfg.StrOpt('quantum_admin_tenant_name', + help='tenant name for connecting to quantum in admin context'), + cfg.StrOpt('quantum_admin_auth_url', + default='http://localhost:5000/v2.0', + help='auth url for connecting to quantum in admin context'), + cfg.StrOpt('quantum_auth_strategy', + default='keystone', + help='auth strategy for connecting to ' + 'quantum in admin context'), + ] + +FLAGS = flags.FLAGS +FLAGS.register_opts(quantum_opts) + +LOG = logging.getLogger(__name__) + + +class API(base.Base): + """API for interacting with the quantum 2.x API.""" + + def setup_networks_on_host(self, context, instance, host=None, + teardown=False): + """Setup or teardown the network structures.""" + + def allocate_for_instance(self, context, instance, **kwargs): + """Allocate all network resources for the instance.""" + LOG.debug(_('allocate_for_instance() for %s'), + instance['display_name']) + search_opts = {} + if instance['project_id']: + search_opts.update({"tenant_id": instance['project_id']}) + else: + msg = _('empty project id for instance %s') + raise exception.InvalidInput( + reason=msg % instance['display_name']) + data = quantumv2.get_client(context).list_networks(**search_opts) + nets = data.get('networks', []) + created_port_ids = [] + for network in nets: + port_req_body = {'port': {'network_id': network['id'], + 'admin_state_up': True, + 'device_id': instance['uuid'], + 'tenant_id': instance['project_id']}, + } + try: + created_port_ids.append( + quantumv2.get_client(context).create_port( + port_req_body)['port']['id']) + except Exception: + with excutils.save_and_reraise_exception(): + for port_id in created_port_ids: + try: + quantumv2.get_client(context).delete_port(port_id) + except Exception as ex: + msg = _("Fail to delete port %(portid)s with" + " failure: %(exception)s") + LOG.debug(msg, {'portid': port_id, + 'exception': ex}) + return self.get_instance_nw_info(context, instance, networks=nets) + + def deallocate_for_instance(self, context, instance, **kwargs): + """Deallocate all network resources related to the instance.""" + LOG.debug(_('deallocate_for_instance() for %s'), + instance['display_name']) + search_opts = {'device_id': instance['uuid']} + data = quantumv2.get_client(context).list_ports(**search_opts) + ports = data.get('ports', []) + for port in ports: + try: + quantumv2.get_client(context).delete_port(port['id']) + except Exception as ex: + with excutils.save_and_reraise_exception(): + msg = _("Fail to delete port %(portid)s with failure:" + "%(exception)s") + LOG.debug(msg, {'portid': port['id'], + 'exception': ex}) + + @refresh_cache + def get_instance_nw_info(self, context, instance, networks=None): + LOG.debug(_('get_instance_nw_info() for %s'), + instance['display_name']) + nw_info = self._build_network_info_model(context, instance, networks) + return network_model.NetworkInfo.hydrate(nw_info) + + def add_fixed_ip_to_instance(self, context, instance, network_id): + """Add a fixed ip to the instance from specified network.""" + raise NotImplemented() + + def remove_fixed_ip_from_instance(self, context, instance, address): + """Remove a fixed ip from the instance.""" + raise NotImplemented() + + def validate_networks(self, context, requested_networks): + """Validate that the tenant has the requested networks.""" + LOG.debug(_('validate_networks() for %s'), + requested_networks) + if not requested_networks: + return + search_opts = {"tenant_id": context.project_id} + net_ids = [net_id for (net_id, _i) in requested_networks] + search_opts['id'] = net_ids + data = quantumv2.get_client(context).list_networks(**search_opts) + nets = data.get('networks', []) + if len(nets) != len(net_ids): + requsted_netid_set = set(net_ids) + returned_netid_set = set([net['id'] for net in nets]) + lostid_set = requsted_netid_set - returned_netid_set + id_str = '' + for _id in lostid_set: + id_str = id_str and id_str + ', ' + _id or _id + raise exception.NetworkNotFound(network_id=id_str) + + def get_instance_uuids_by_ip_filter(self, context, filters): + """Return a list of dicts in the form of + [{'instance_uuid': uuid}] that matched the ip filter. + """ + # filters['ip'] is composed as '^%s$' % fixed_ip.replace('.', '\\.') + ip = filters.get('ip') + # we remove ^$\ in the ip filer + if ip[0] == '^': + ip = ip[1:] + if ip[-1] == '$': + ip = ip[:-1] + ip = ip.replace('\\.', '.') + search_opts = {"fixed_ips": {'ip_address': ip}} + data = quantumv2.get_client(context).list_ports(**search_opts) + ports = data.get('ports', []) + + return [{'instance_uuid': port['device_id']} for port in ports + if port['device_id']] + + @refresh_cache + def associate_floating_ip(self, context, instance, + floating_address, fixed_address, + affect_auto_assigned=False): + """Associate a floating ip with a fixed ip.""" + raise NotImplemented() + + def get_all(self, context): + raise NotImplemented() + + def get(self, context, network_uuid): + raise NotImplemented() + + def delete(self, context, network_uuid): + raise NotImplemented() + + def disassociate(self, context, network_uuid): + raise NotImplemented() + + def get_fixed_ip(self, context, id): + raise NotImplemented() + + def get_fixed_ip_by_address(self, context, address): + raise NotImplemented() + + def get_floating_ip(self, context, id): + raise NotImplemented() + + def get_floating_ip_pools(self, context): + raise NotImplemented() + + def get_floating_ip_by_address(self, context, address): + raise NotImplemented() + + def get_floating_ips_by_project(self, context): + raise NotImplemented() + + def get_floating_ips_by_fixed_address(self, context, fixed_address): + raise NotImplemented() + + def get_instance_id_by_floating_address(self, context, address): + raise NotImplemented() + + def get_vifs_by_instance(self, context, instance): + raise NotImplemented() + + def get_vif_by_mac_address(self, context, mac_address): + raise NotImplemented() + + def allocate_floating_ip(self, context, pool=None): + """Add a floating ip to a project from a pool.""" + raise NotImplemented() + + def release_floating_ip(self, context, address, + affect_auto_assigned=False): + """Remove a floating ip with the given address from a project.""" + raise NotImplemented() + + @refresh_cache + def disassociate_floating_ip(self, context, instance, address, + affect_auto_assigned=False): + """Disassociate a floating ip from the fixed ip + it is associated with.""" + raise NotImplemented() + + def add_network_to_project(self, context, project_id): + """Force add a network to the project.""" + raise NotImplemented() + + def _build_network_info_model(self, context, instance, networks=None): + search_opts = {'tenant_id': instance['project_id'], + 'device_id': instance['uuid'], } + data = quantumv2.get_client(context).list_ports(**search_opts) + ports = data.get('ports', []) + if not networks: + search_opts = {} + if instance['project_id']: + search_opts.update({"tenant_id": instance['project_id']}) + data = quantumv2.get_client(context).list_networks(**search_opts) + networks = data.get('networks', []) + nw_info = network_model.NetworkInfo() + for port in ports: + network_name = None + for net in networks: + if port['network_id'] == net['id']: + network_name = net['name'] + break + + subnets = self._get_subnets_from_port(context, port) + network_IPs = [network_model.FixedIP(address=ip_address) + for ip_address in [ip['ip_address'] + for ip in port['fixed_ips']]] + # TODO(gongysh) get floating_ips for each fixed_ip + + for subnet in subnets: + subnet['ips'] = [fixed_ip for fixed_ip in network_IPs + if fixed_ip.is_in_subnet(subnet)] + + network = network_model.Network( + id=port['network_id'], + bridge='', # Quantum ignores this field + injected=FLAGS.flat_injected, + label=network_name, + tenant_id=net['tenant_id'] + ) + network['subnets'] = subnets + nw_info.append(network_model.VIF( + id=port['id'], + address=port['mac_address'], + network=network)) + return nw_info + + def _get_subnets_from_port(self, context, port): + """Return the subnets for a given port.""" + + fixed_ips = port['fixed_ips'] + search_opts = {'id': [ip['subnet_id'] for ip in fixed_ips]} + data = quantumv2.get_client(context).list_subnets(**search_opts) + ipam_subnets = data.get('subnets', []) + subnets = [] + for subnet in ipam_subnets: + subnet_dict = {'cidr': subnet['cidr'], + 'gateway': network_model.IP( + address=subnet['gateway_ip'], + type='gateway'), + } + # TODO(gongysh) deal with dhcp + + subnet_object = network_model.Subnet(**subnet_dict) + for dns in subnet.get('dns_nameservers', []): + subnet_object.add_dns( + network_model.IP(address=dns, type='dns')) + + # TODO(gongysh) get the routes for this subnet + subnets.append(subnet_object) + return subnets + + def get_dns_domains(self, context): + """Return a list of available dns domains. + + These can be used to create DNS entries for floating ips. + """ + raise NotImplemented() + + def add_dns_entry(self, context, address, name, dns_type, domain): + """Create specified DNS entry for address.""" + raise NotImplemented() + + def modify_dns_entry(self, context, name, address, domain): + """Create specified DNS entry for address.""" + raise NotImplemented() + + def delete_dns_entry(self, context, name, domain): + """Delete the specified dns entry.""" + raise NotImplemented() + + def delete_dns_domain(self, context, domain): + """Delete the specified dns domain.""" + raise NotImplemented() + + def get_dns_entries_by_address(self, context, address, domain): + """Get entries for address and domain.""" + raise NotImplemented() + + def get_dns_entries_by_name(self, context, name, domain): + """Get entries for name and domain.""" + raise NotImplemented() + + def create_private_dns_domain(self, context, domain, availability_zone): + """Create a private DNS domain with nova availability zone.""" + raise NotImplemented() + + def create_public_dns_domain(self, context, domain, project=None): + """Create a private DNS domain with optional nova project.""" + raise NotImplemented() diff --git a/nova/tests/network/test_quantum.py b/nova/tests/network/test_quantum.py index cd6c4b8cb..5299bde93 100644 --- a/nova/tests/network/test_quantum.py +++ b/nova/tests/network/test_quantum.py @@ -291,7 +291,7 @@ class QuantumDeallocationTestCase(QuantumNovaTestCase): self.net_man.deallocate_ip_address('context', 'net_id', 'project_id', {'uuid': 1}, 'instance_id') - def test_deallocate_ip_address(self): + def test_deallocate_ip_address_2(self): ipam = self.mox.CreateMock(melange_ipam_lib.QuantumMelangeIPAMLib) ipam.get_tenant_id_by_net_id('context', 'net_id', {'uuid': 1}, 'project_id').AndRaise(Exception()) @@ -492,7 +492,7 @@ class QuantumManagerTestCase(QuantumNovaTestCase): net = db.network_get_by_uuid(ctx.elevated(), net_id) self.assertTrue(net is not None) self.assertEquals(net['uuid'], net_id) - self.assertTrue(net['host'] is not None) + self.assertTrue(net['host'] != None) class QuantumNovaMACGenerationTestCase(QuantumNovaTestCase): diff --git a/nova/tests/network/test_quantumv2.py b/nova/tests/network/test_quantumv2.py new file mode 100644 index 000000000..f24c60e69 --- /dev/null +++ b/nova/tests/network/test_quantumv2.py @@ -0,0 +1,406 @@ +# 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. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import mox + +from nova import context +from nova import exception +from nova.network import quantumv2 +from nova.network import model +from nova.network.quantumv2 import api as quantumapi +from nova.openstack.common import cfg +from nova import test +from nova import utils +from quantumclient.v2_0 import client + +FLAGS = cfg.CONF + + +class MyComparator(mox.Comparator): + def __init__(self, lhs): + self.lhs = lhs + + def _com_dict(self, lhs, rhs): + if len(lhs) != len(rhs): + return False + for key, value in lhs.iteritems(): + if key not in rhs: + return False + rhs_value = rhs[key] + if not self._com(value, rhs_value): + return False + return True + + def _com_list(self, lhs, rhs): + if len(lhs) != len(rhs): + return False + for lhs_value in lhs: + if lhs_value not in rhs: + return False + return True + + def _com(self, lhs, rhs): + if lhs is None: + return rhs is None + if isinstance(lhs, dict): + if not isinstance(rhs, dict): + return False + return self._com_dict(lhs, rhs) + if isinstance(lhs, list): + if not isinstance(rhs, list): + return False + return self._com_list(lhs, rhs) + if isinstance(lhs, tuple): + if not isinstance(rhs, tuple): + return False + return self._com_list(lhs, rhs) + return lhs == rhs + + def equals(self, rhs): + return self._com(self.lhs, rhs) + + def __repr__(self): + return str(self.lhs) + + +class TestQuantumClient(test.TestCase): + def setUp(self): + super(TestQuantumClient, self).setUp() + + def test_withtoken(self): + self.flags(quantum_url='http://anyhost/') + self.flags(quantum_url_timeout=30) + my_context = context.RequestContext('userid', + 'my_tenantid', + auth_token='token') + self.mox.StubOutWithMock(client.Client, "__init__") + client.Client.__init__( + endpoint_url=FLAGS.quantum_url, + token=my_context.auth_token, + timeout=FLAGS.quantum_url_timeout).AndReturn(None) + self.mox.ReplayAll() + quantumv2.get_client(my_context) + + def test_withouttoken_keystone_connection_error(self): + self.flags(quantum_auth_strategy='keystone') + self.flags(quantum_url='http://anyhost/') + my_context = context.RequestContext('userid', 'my_tenantid') + self.assertRaises(Exception, + quantumv2.get_client, + my_context) + + def test_withouttoken_keystone_not_auth(self): + # self.flags(quantum_auth_strategy=None) fail to work + old_quantum_auth_strategy = FLAGS.quantum_auth_strategy + setattr(FLAGS, 'quantum_auth_strategy', None) + self.flags(quantum_url='http://anyhost/') + self.flags(quantum_url_timeout=30) + my_context = context.RequestContext('userid', 'my_tenantid') + self.mox.StubOutWithMock(client.Client, "__init__") + client.Client.__init__( + endpoint_url=FLAGS.quantum_url, + auth_strategy=None, + timeout=FLAGS.quantum_url_timeout).AndReturn(None) + self.mox.ReplayAll() + try: + quantumv2.get_client(my_context) + finally: + setattr(FLAGS, 'quantum_auth_strategy', + old_quantum_auth_strategy) + + +class TestQuantumv2(test.TestCase): + + def setUp(self): + super(TestQuantumv2, self).setUp() + self.mox.StubOutWithMock(quantumv2, 'get_client') + self.moxed_client = self.mox.CreateMock(client.Client) + quantumv2.get_client(mox.IgnoreArg()).MultipleTimes().AndReturn( + self.moxed_client) + self.context = context.RequestContext('userid', 'my_tenantid') + setattr(self.context, + 'auth_token', + 'bff4a5a6b9eb4ea2a6efec6eefb77936') + self.instance = {'project_id': '9d049e4b60b64716978ab415e6fbd5c0', + 'uuid': str(utils.gen_uuid()), + 'display_name': 'test_instance'} + self.nets1 = [{'id': 'my_netid1', + 'name': 'my_netname1', + 'tenant_id': 'my_tenantid'}] + self.nets2 = [] + self.nets2.append(self.nets1[0]) + self.nets2.append({'id': 'my_netid2', + 'name': 'my_netname2', + 'tenant_id': 'my_tenantid'}) + + self.port_data1 = [{'network_id': 'my_netid1', + 'device_id': 'device_id1', + 'id': 'my_portid1', + 'fixed_ips': [{'ip_address': '10.0.1.2', + 'subnet_id': 'my_subid1'}], + 'mac_address': 'my_mac1', }] + self.port_data2 = [] + self.port_data2.append(self.port_data1[0]) + self.port_data2.append({'network_id': 'my_netid2', + 'device_id': 'device_id2', + 'id': 'my_portid2', + 'fixed_ips': [{'ip_address': '10.0.2.2', + 'subnet_id': 'my_subid2'}], + 'mac_address': 'my_mac2', }) + self.subnet_data1 = [{'cidr': '10.0.1.0/24', + 'gateway_ip': '10.0.1.1', + 'dns_nameservers': ['8.8.1.1', '8.8.1.2']}] + self.subnet_data2 = [] + self.subnet_data2.append({'cidr': '10.0.2.0/24', + 'gateway_ip': '10.0.2.1', + 'dns_nameservers': ['8.8.2.1', '8.8.2.2']}) + + def tearDown(self): + try: + self.mox.UnsetStubs() + self.mox.VerifyAll() + finally: + FLAGS.reset() + + def _verify_nw_info(self, nw_inf, index=0): + id_suffix = index + 1 + self.assertEquals('10.0.%s.2' % id_suffix, + nw_inf.fixed_ips()[index]['address']) + self.assertEquals('my_netname%s' % id_suffix, + nw_inf[index]['network']['label']) + self.assertEquals('my_portid%s' % id_suffix, nw_inf[index]['id']) + self.assertEquals('my_mac%s' % id_suffix, nw_inf[index]['address']) + self.assertEquals('10.0.%s.0/24' % id_suffix, + nw_inf[index]['network']['subnets'][0]['cidr']) + self.assertTrue(model.IP(address='8.8.%s.1' % id_suffix) in + nw_inf[index]['network']['subnets'][0]['dns']) + + def _get_instance_nw_info(self, number): + api = quantumapi.API() + self.mox.StubOutWithMock(api.db, 'instance_info_cache_update') + api.db.instance_info_cache_update(mox.IgnoreArg(), + self.instance['uuid'], + mox.IgnoreArg()) + port_data = number == 1 and self.port_data1 or self.port_data2 + self.moxed_client.list_ports( + tenant_id=self.instance['project_id'], + device_id=self.instance['uuid']).AndReturn( + {'ports': port_data}) + nets = number == 1 and self.nets1 or self.nets2 + self.moxed_client.list_networks( + tenant_id=self.instance['project_id']).AndReturn( + {'networks': nets}) + for i in xrange(1, number + 1): + subnet_data = i == 1 and self.subnet_data1 or self.subnet_data2 + self.moxed_client.list_subnets( + id=mox.SameElementsAs(['my_subid%s' % i])).AndReturn( + {'subnets': subnet_data}) + self.mox.ReplayAll() + nw_inf = api.get_instance_nw_info(self.context, self.instance) + for i in xrange(0, number): + self._verify_nw_info(nw_inf, i) + + def test_get_instance_nw_info_1(self): + """Test to get one port in one network and subnet.""" + self._get_instance_nw_info(1) + + def test_get_instance_nw_info_2(self): + """Test to get one port in each of two networks and subnets.""" + self._get_instance_nw_info(2) + + def test_get_instance_nw_info_with_nets(self): + """Test get instance_nw_info with networks passed in.""" + api = quantumapi.API() + self.mox.StubOutWithMock(api.db, 'instance_info_cache_update') + api.db.instance_info_cache_update( + mox.IgnoreArg(), + self.instance['uuid'], mox.IgnoreArg()) + self.moxed_client.list_ports( + tenant_id=self.instance['project_id'], + device_id=self.instance['uuid']).AndReturn( + {'ports': self.port_data1}) + self.moxed_client.list_subnets( + id=mox.SameElementsAs(['my_subid1'])).AndReturn( + {'subnets': self.subnet_data1}) + self.mox.ReplayAll() + nw_inf = api.get_instance_nw_info(self.context, + self.instance, + networks=self.nets1) + self._verify_nw_info(nw_inf, 0) + + def _allocate_for_instance(self, number): + api = quantumapi.API() + self.mox.StubOutWithMock(api, 'get_instance_nw_info') + nets = number == 1 and self.nets1 or self.nets2 + api.get_instance_nw_info(mox.IgnoreArg(), + self.instance, + networks=nets).AndReturn(None) + + self.moxed_client.list_networks( + tenant_id=self.instance['project_id']).AndReturn( + {'networks': nets}) + for network in nets: + port_req_body = { + 'port': { + 'network_id': network['id'], + 'admin_state_up': True, + 'device_id': self.instance['uuid'], + 'tenant_id': self.instance['project_id'], + }, + } + port = {'id': 'portid_' + network['id']} + self.moxed_client.create_port( + MyComparator(port_req_body)).AndReturn({'port': port}) + self.mox.ReplayAll() + api.allocate_for_instance(self.context, self.instance) + + def test_allocate_for_instance_1(self): + """Allocate one port in one network env.""" + self._allocate_for_instance(1) + + def test_allocate_for_instance_2(self): + """Allocate one port in two networks env.""" + self._allocate_for_instance(2) + + def test_allocate_for_instance_ex1(self): + """verify we will delete created ports + if we fail to allocate all net resources. + + Mox to raise exception when creating a second port. + In this case, the code should delete the first created port. + """ + api = quantumapi.API() + self.moxed_client.list_networks( + tenant_id=self.instance['project_id']).AndReturn( + {'networks': self.nets2}) + index = 0 + for network in self.nets2: + port_req_body = { + 'port': { + 'network_id': network['id'], + 'admin_state_up': True, + 'device_id': self.instance['uuid'], + 'tenant_id': self.instance['project_id'], + }, + } + port = {'id': 'portid_' + network['id']} + if index == 0: + self.moxed_client.create_port( + MyComparator(port_req_body)).AndReturn({'port': port}) + else: + self.moxed_client.create_port( + MyComparator(port_req_body)).AndRaise( + Exception("fail to create port")) + index += 1 + self.moxed_client.delete_port('portid_' + self.nets2[0]['id']) + self.mox.ReplayAll() + self.assertRaises(Exception, api.allocate_for_instance, + self.context, self.instance) + + def test_allocate_for_instance_ex2(self): + """verify we have no port to delete + if we fail to allocate the first net resource. + + Mox to raise exception when creating the first port. + In this case, the code should not delete any ports. + """ + api = quantumapi.API() + self.moxed_client.list_networks( + tenant_id=self.instance['project_id']).AndReturn( + {'networks': self.nets2}) + port_req_body = { + 'port': { + 'network_id': self.nets2[0]['id'], + 'admin_state_up': True, + 'device_id': self.instance['uuid'], + 'tenant_id': self.instance['project_id'], + }, + } + self.moxed_client.create_port( + MyComparator(port_req_body)).AndRaise( + Exception("fail to create port")) + self.mox.ReplayAll() + self.assertRaises(Exception, api.allocate_for_instance, + self.context, self.instance) + + def _deallocate_for_instance(self, number): + port_data = number == 1 and self.port_data1 or self.port_data2 + self.moxed_client.list_ports( + device_id=self.instance['uuid']).AndReturn( + {'ports': port_data}) + for port in port_data: + self.moxed_client.delete_port(port['id']) + self.mox.ReplayAll() + api = quantumapi.API() + api.deallocate_for_instance(self.context, self.instance) + + def test_deallocate_for_instance_1(self): + """Test to deallocate in one port env.""" + self._deallocate_for_instance(1) + + def test_deallocate_for_instance_2(self): + """Test to deallocate in two ports env.""" + self._deallocate_for_instance(2) + + def test_validate_networks(self): + requested_networks = [('my_netid1', 'test'), ('my_netid2', 'test2')] + self.moxed_client.list_networks( + id=mox.SameElementsAs(['my_netid1', 'my_netid2']), + tenant_id=self.context.project_id).AndReturn( + {'networks': self.nets2}) + self.mox.ReplayAll() + api = quantumapi.API() + api.validate_networks(self.context, requested_networks) + + def test_validate_networks_ex_1(self): + requested_networks = [('my_netid1', 'test'), ('my_netid2', 'test2')] + self.moxed_client.list_networks( + id=mox.SameElementsAs(['my_netid1', 'my_netid2']), + tenant_id=self.context.project_id).AndReturn( + {'networks': self.nets1}) + self.mox.ReplayAll() + api = quantumapi.API() + try: + api.validate_networks(self.context, requested_networks) + except exception.NetworkNotFound as ex: + self.assertTrue("my_netid2" in str(ex)) + + def test_validate_networks_ex_2(self): + requested_networks = [('my_netid1', 'test'), + ('my_netid2', 'test2'), + ('my_netid3', 'test3')] + self.moxed_client.list_networks( + id=mox.SameElementsAs(['my_netid1', 'my_netid2', 'my_netid3']), + tenant_id=self.context.project_id).AndReturn( + {'networks': self.nets1}) + self.mox.ReplayAll() + api = quantumapi.API() + try: + api.validate_networks(self.context, requested_networks) + except exception.NetworkNotFound as ex: + self.assertTrue("my_netid2, my_netid3" in str(ex)) + + def test_get_instance_uuids_by_ip_filter(self): + filters = {'ip': '^10\\.0\\.1\\.2$'} + self.moxed_client.list_ports( + fixed_ips=MyComparator({'ip_address': '10.0.1.2'})).AndReturn( + {'ports': self.port_data2}) + self.mox.ReplayAll() + api = quantumapi.API() + result = api.get_instance_uuids_by_ip_filter(self.context, filters) + self.assertEquals('device_id1', result[0]['instance_uuid']) + self.assertEquals('device_id2', result[1]['instance_uuid']) diff --git a/tools/pip-requires b/tools/pip-requires index 234c31a7b..010289137 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -24,3 +24,4 @@ Babel>=0.9.6 iso8601>=0.1.4 httplib2 setuptools_git>=0.4 +python-quantumclient>=0.1,<0.2 |
