summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvladimir.p <vladimir@zadarastorage.com>2011-08-25 19:18:46 -0700
committervladimir.p <vladimir@zadarastorage.com>2011-08-25 19:18:46 -0700
commit8b4d46285b7f00a89bad6000fdc78cc2ab1d5608 (patch)
treec7756786f2fa32315cd15a59f3f3b09c10196254
parent59e9adb8e2ef39474a04ead76975a1fc3f913550 (diff)
parente38ba3a6066de8499ea5cc8d1fb2e5daefbf336a (diff)
downloadnova-8b4d46285b7f00a89bad6000fdc78cc2ab1d5608.tar.gz
nova-8b4d46285b7f00a89bad6000fdc78cc2ab1d5608.tar.xz
nova-8b4d46285b7f00a89bad6000fdc78cc2ab1d5608.zip
merged with rev.1499
-rw-r--r--Authors1
-rwxr-xr-xbin/nova-manage33
-rw-r--r--etc/nova/api-paste.ini6
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py18
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py12
-rw-r--r--nova/log.py6
-rw-r--r--nova/tests/test_instance_types.py68
-rw-r--r--nova/tests/test_nova_manage.py154
-rw-r--r--nova/tests/test_test_utils.py41
-rw-r--r--nova/tests/test_virt_drivers.py489
-rw-r--r--nova/tests/utils.py68
-rw-r--r--nova/virt/driver.py18
-rw-r--r--nova/virt/fake.py13
-rw-r--r--nova/virt/xenapi/vmops.py5
-rw-r--r--nova/volume/driver.py6
15 files changed, 907 insertions, 31 deletions
diff --git a/Authors b/Authors
index 2d2cde97e..b9e7a7d23 100644
--- a/Authors
+++ b/Authors
@@ -69,6 +69,7 @@ Koji Iida <iida.koji@lab.ntt.co.jp>
Lorin Hochstein <lorin@isi.edu>
Lvov Maxim <usrleon@gmail.com>
Mandell Degerness <mdegerne@gmail.com>
+Mark McLoughlin <markmc@redhat.com>
Mark Washenberger <mark.washenberger@rackspace.com>
Masanori Itoh <itoumsn@nttdata.co.jp>
Matt Dietz <matt.dietz@rackspace.com>
diff --git a/bin/nova-manage b/bin/nova-manage
index 977ad5c66..76e188596 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -801,6 +801,39 @@ class NetworkCommands(object):
' before delete' % network.project_id))
db.network_delete_safe(context.get_admin_context(), network.id)
+ @args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>',
+ help='Network to modify')
+ @args('--project', dest="project", metavar='<project name>',
+ help='Project name to associate')
+ @args('--host', dest="host", metavar='<host>',
+ help='Host to associate')
+ @args('--disassociate-project', action="store_true", dest='dis_project',
+ default=False, help='Disassociate Network from Project')
+ @args('--disassociate-host', action="store_true", dest='dis_host',
+ default=False, help='Disassociate Host from Project')
+ def modify(self, fixed_range, project=None, host=None,
+ dis_project=None, dis_host=None):
+ """Associate/Disassociate Network with Project and/or Host
+ arguments: network project host
+ leave any field blank to ignore it
+ """
+ admin_context = context.get_admin_context()
+ network = db.network_get_by_cidr(admin_context, fixed_range)
+ net = {}
+ #User can choose the following actions each for project and host.
+ #1) Associate (set not None value given by project/host parameter)
+ #2) Disassociate (set None by disassociate parameter)
+ #3) Keep unchanged (project/host key is not added to 'net')
+ if project:
+ net['project_id'] = project
+ elif dis_project:
+ net['project_id'] = None
+ if host:
+ net['host'] = host
+ elif dis_host:
+ net['host'] = None
+ db.network_update(admin_context, network['id'], net)
+
class VmCommands(object):
"""Class for mangaging VM instances."""
diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini
index dafdef877..cd24efb13 100644
--- a/etc/nova/api-paste.ini
+++ b/etc/nova/api-paste.ini
@@ -26,7 +26,11 @@ pipeline = logrequest ec2noauth cloudrequest authorizer ec2executor
# pipeline = logrequest totoken authtoken keystonecontext cloudrequest authorizer ec2executor
[pipeline:ec2admin]
-pipeline = logrequest authenticate adminrequest authorizer ec2executor
+pipeline = logrequest ec2noauth adminrequest authorizer ec2executor
+# NOTE(vish): use the following pipeline for deprecated auth
+#pipeline = logrequest authenticate adminrequest authorizer ec2executor
+# NOTE(vish): use the following pipeline for keystone
+#pipeline = logrequest totoken authtoken keystonecontext adminrequest authorizer ec2executor
[pipeline:ec2metadata]
pipeline = logrequest ec2md
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
index a4fe3e482..56b287171 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
@@ -75,8 +75,8 @@ def new_style_quotas_table(name):
)
-def existing_quotas_table(migrate_engine):
- return Table('quotas', meta, autoload=True, autoload_with=migrate_engine)
+def quotas_table(migrate_engine, name='quotas'):
+ return Table(name, meta, autoload=True, autoload_with=migrate_engine)
def _assert_no_duplicate_project_ids(quotas):
@@ -179,13 +179,18 @@ def upgrade(migrate_engine):
# bind migrate_engine to your metadata
meta.bind = migrate_engine
- old_quotas = existing_quotas_table(migrate_engine)
+ old_quotas = quotas_table(migrate_engine)
assert_old_quotas_have_no_active_duplicates(migrate_engine, old_quotas)
new_quotas = new_style_quotas_table('quotas_new')
new_quotas.create()
convert_forward(migrate_engine, old_quotas, new_quotas)
old_quotas.drop()
+
+ # clear metadata to work around this:
+ # http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=128
+ meta.clear()
+ new_quotas = quotas_table(migrate_engine, 'quotas_new')
new_quotas.rename('quotas')
@@ -193,11 +198,16 @@ def downgrade(migrate_engine):
# Operations to reverse the above upgrade go here.
meta.bind = migrate_engine
- new_quotas = existing_quotas_table(migrate_engine)
+ new_quotas = quotas_table(migrate_engine)
assert_new_quotas_have_no_active_duplicates(migrate_engine, new_quotas)
old_quotas = old_style_quotas_table('quotas_old')
old_quotas.create()
convert_backward(migrate_engine, old_quotas, new_quotas)
new_quotas.drop()
+
+ # clear metadata to work around this:
+ # http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=128
+ meta.clear()
+ old_quotas = quotas_table(migrate_engine, 'quotas_old')
old_quotas.rename('quotas')
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py
index f3244033b..dfbd4ba32 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py
@@ -40,13 +40,17 @@ def upgrade(migrate_engine):
migrations.create_column(new_instance_type_id)
# Convert flavor_id to instance_type_id
+ itypes = {}
for instance_type in migrate_engine.execute(instance_types.select()):
+ itypes[instance_type.id] = instance_type.flavorid
+
+ for instance_type_id in itypes.keys():
migrate_engine.execute(migrations.update()\
- .where(migrations.c.old_flavor_id == instance_type.flavorid)\
- .values(old_instance_type_id=instance_type.id))
+ .where(migrations.c.old_flavor_id == itypes[instance_type_id])\
+ .values(old_instance_type_id=instance_type_id))
migrate_engine.execute(migrations.update()\
- .where(migrations.c.new_flavor_id == instance_type.flavorid)\
- .values(new_instance_type_id=instance_type.id))
+ .where(migrations.c.new_flavor_id == itypes[instance_type_id])\
+ .values(new_instance_type_id=instance_type_id))
migrations.c.old_flavor_id.drop()
migrations.c.new_flavor_id.drop()
diff --git a/nova/log.py b/nova/log.py
index 222b8c5fb..eb0b6020f 100644
--- a/nova/log.py
+++ b/nova/log.py
@@ -32,6 +32,7 @@ import json
import logging
import logging.handlers
import os
+import stat
import sys
import traceback
@@ -257,7 +258,10 @@ class NovaRootLogger(NovaLogger):
self.filelog = WatchedFileHandler(logpath)
self.addHandler(self.filelog)
self.logpath = logpath
- os.chmod(self.logpath, FLAGS.logfile_mode)
+
+ st = os.stat(self.logpath)
+ if st.st_mode != (stat.S_IFREG | FLAGS.logfile_mode):
+ os.chmod(self.logpath, FLAGS.logfile_mode)
else:
self.removeHandler(self.filelog)
self.addHandler(self.streamlog)
diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py
index ef271518c..09f532239 100644
--- a/nova/tests/test_instance_types.py
+++ b/nova/tests/test_instance_types.py
@@ -47,6 +47,29 @@ class InstanceTypeTestCase(test.TestCase):
self.id = max_id["id"] + 1
self.name = str(int(time.time()))
+ def _nonexistent_flavor_name(self):
+ """return an instance type name not in the DB"""
+ nonexistent_flavor = "sdfsfsdf"
+ flavors = instance_types.get_all_types()
+ while nonexistent_flavor in flavors:
+ nonexistent_flavor += "z"
+ else:
+ return nonexistent_flavor
+
+ def _nonexistent_flavor_id(self):
+ """return an instance type ID not in the DB"""
+ nonexistent_flavor = 2700
+ flavor_ids = [value["id"] for key, value in\
+ instance_types.get_all_types().iteritems()]
+ while nonexistent_flavor in flavor_ids:
+ nonexistent_flavor += 1
+ else:
+ return nonexistent_flavor
+
+ def _existing_flavor(self):
+ """return first instance type name"""
+ return instance_types.get_all_types().keys()[0]
+
def test_instance_type_create_then_delete(self):
"""Ensure instance types can be created"""
starting_inst_list = instance_types.get_all_types()
@@ -84,10 +107,11 @@ class InstanceTypeTestCase(test.TestCase):
exception.InvalidInput,
instance_types.create, self.name, 256, 1, "aa", self.flavorid)
- def test_non_existant_inst_type_shouldnt_delete(self):
+ def test_non_existent_inst_type_shouldnt_delete(self):
"""Ensures that instance type creation fails with invalid args"""
self.assertRaises(exception.ApiError,
- instance_types.destroy, "sfsfsdfdfs")
+ instance_types.destroy,
+ self._nonexistent_flavor_name())
def test_repeated_inst_types_should_raise_api_error(self):
"""Ensures that instance duplicates raises ApiError"""
@@ -97,3 +121,43 @@ class InstanceTypeTestCase(test.TestCase):
self.assertRaises(
exception.ApiError,
instance_types.create, new_name, 256, 1, 120, self.flavorid)
+
+ def test_will_not_destroy_with_no_name(self):
+ """Ensure destroy sad path of no name raises error"""
+ self.assertRaises(exception.ApiError,
+ instance_types.destroy,
+ self._nonexistent_flavor_name())
+
+ def test_will_not_purge_without_name(self):
+ """Ensure purge without a name raises error"""
+ self.assertRaises(exception.InvalidInstanceType,
+ instance_types.purge, None)
+
+ def test_will_not_purge_with_wrong_name(self):
+ """Ensure purge without correct name raises error"""
+ self.assertRaises(exception.ApiError,
+ instance_types.purge,
+ self._nonexistent_flavor_name())
+
+ def test_will_not_get_bad_default_instance_type(self):
+ """ensures error raised on bad default instance type"""
+ FLAGS.default_instance_type = self._nonexistent_flavor_name()
+ self.assertRaises(exception.InstanceTypeNotFoundByName,
+ instance_types.get_default_instance_type)
+
+ def test_will_not_get_instance_type_by_name_with_no_name(self):
+ """Ensure get by name returns default flavor with no name"""
+ self.assertEqual(instance_types.get_default_instance_type(),
+ instance_types.get_instance_type_by_name(None))
+
+ def test_will_not_get_instance_type_with_bad_name(self):
+ """Ensure get by name returns default flavor with bad name"""
+ self.assertRaises(exception.InstanceTypeNotFound,
+ instance_types.get_instance_type,
+ self._nonexistent_flavor_name())
+
+ def test_will_not_get_flavor_by_bad_flavor_id(self):
+ """Ensure get by flavor raises error with wrong flavorid"""
+ self.assertRaises(exception.InstanceTypeNotFound,
+ instance_types.get_instance_type_by_name,
+ self._nonexistent_flavor_id())
diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py
index f5ea68a03..520bfbea1 100644
--- a/nova/tests/test_nova_manage.py
+++ b/nova/tests/test_nova_manage.py
@@ -31,6 +31,7 @@ sys.dont_write_bytecode = False
import mox
import stubout
+import StringIO
from nova import context
from nova import db
from nova import exception
@@ -70,3 +71,156 @@ class FixedIpCommandsTestCase(test.TestCase):
self.assertRaises(SystemExit,
self.commands.unreserve,
'55.55.55.55')
+
+
+class NetworkCommandsTestCase(test.TestCase):
+ def setUp(self):
+ super(NetworkCommandsTestCase, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ self.commands = nova_manage.NetworkCommands()
+ self.context = context.get_admin_context()
+ self.net = {'id': 0,
+ 'label': 'fake',
+ 'injected': False,
+ 'cidr': '192.168.0.0/24',
+ 'cidr_v6': 'dead:beef::/64',
+ 'multi_host': False,
+ 'gateway_v6': 'dead:beef::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': 'fa0',
+ 'bridge_interface': 'fake_fa0',
+ 'gateway': '192.168.0.1',
+ 'broadcast': '192.168.0.255',
+ 'dns1': '8.8.8.8',
+ 'dns2': '8.8.4.4',
+ 'vlan': 200,
+ 'vpn_public_address': '10.0.0.2',
+ 'vpn_public_port': '2222',
+ 'vpn_private_address': '192.168.0.2',
+ 'dhcp_start': '192.168.0.3',
+ 'project_id': 'fake_project',
+ 'host': 'fake_host',
+ 'uuid': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'}
+
+ def fake_network_get_by_cidr(context, cidr):
+ self.assertTrue(context.to_dict()['is_admin'])
+ self.assertEqual(cidr, self.fake_net['cidr'])
+ return db_fakes.FakeModel(self.fake_net)
+
+ def fake_network_update(context, network_id, values):
+ self.assertTrue(context.to_dict()['is_admin'])
+ self.assertEqual(network_id, self.fake_net['id'])
+ self.assertEqual(values, self.fake_update_value)
+ self.fake_network_get_by_cidr = fake_network_get_by_cidr
+ self.fake_network_update = fake_network_update
+
+ def tearDown(self):
+ super(NetworkCommandsTestCase, self).tearDown()
+ self.stubs.UnsetAll()
+
+ def test_create(self):
+
+ def fake_create_networks(obj, context, **kwargs):
+ self.assertTrue(context.to_dict()['is_admin'])
+ self.assertEqual(kwargs['label'], 'Test')
+ self.assertEqual(kwargs['cidr'], '10.2.0.0/24')
+ self.assertEqual(kwargs['multi_host'], False)
+ self.assertEqual(kwargs['num_networks'], 1)
+ self.assertEqual(kwargs['network_size'], 256)
+ self.assertEqual(kwargs['vlan_start'], 200)
+ self.assertEqual(kwargs['vpn_start'], 2000)
+ self.assertEqual(kwargs['cidr_v6'], 'fd00:2::/120')
+ self.assertEqual(kwargs['gateway_v6'], 'fd00:2::22')
+ self.assertEqual(kwargs['bridge'], 'br200')
+ self.assertEqual(kwargs['bridge_interface'], 'eth0')
+ self.assertEqual(kwargs['dns1'], '8.8.8.8')
+ self.assertEqual(kwargs['dns2'], '8.8.4.4')
+ self.flags(network_manager='nova.network.manager.VlanManager')
+ from nova.network import manager as net_manager
+ self.stubs.Set(net_manager.VlanManager, 'create_networks',
+ fake_create_networks)
+ self.commands.create(
+ label='Test',
+ fixed_range_v4='10.2.0.0/24',
+ num_networks=1,
+ network_size=256,
+ multi_host='F',
+ vlan_start=200,
+ vpn_start=2000,
+ fixed_range_v6='fd00:2::/120',
+ gateway_v6='fd00:2::22',
+ bridge='br200',
+ bridge_interface='eth0',
+ dns1='8.8.8.8',
+ dns2='8.8.4.4')
+
+ def test_list(self):
+
+ def fake_network_get_all(context):
+ return [db_fakes.FakeModel(self.net)]
+ self.stubs.Set(db, 'network_get_all', fake_network_get_all)
+ output = StringIO.StringIO()
+ sys.stdout = output
+ self.commands.list()
+ sys.stdout = sys.__stdout__
+ result = output.getvalue()
+ _fmt = "%(id)-5s\t%(cidr)-18s\t%(cidr_v6)-15s\t%(dhcp_start)-15s\t" +\
+ "%(dns1)-15s\t%(dns2)-15s\t%(vlan)-15s\t%(project_id)-15s\t" +\
+ "%(uuid)-15s"
+ head = _fmt % {'id': _('id'),
+ 'cidr': _('IPv4'),
+ 'cidr_v6': _('IPv6'),
+ 'dhcp_start': _('start address'),
+ 'dns1': _('DNS1'),
+ 'dns2': _('DNS2'),
+ 'vlan': _('VlanID'),
+ 'project_id': _('project'),
+ 'uuid': _("uuid")}
+ body = _fmt % {'id': self.net['id'],
+ 'cidr': self.net['cidr'],
+ 'cidr_v6': self.net['cidr_v6'],
+ 'dhcp_start': self.net['dhcp_start'],
+ 'dns1': self.net['dns1'],
+ 'dns2': self.net['dns2'],
+ 'vlan': self.net['vlan'],
+ 'project_id': self.net['project_id'],
+ 'uuid': self.net['uuid']}
+ answer = '%s\n%s\n' % (head, body)
+ self.assertEqual(result, answer)
+
+ def test_delete(self):
+ self.fake_net = self.net
+ self.fake_net['project_id'] = None
+ self.fake_net['host'] = None
+ self.stubs.Set(db, 'network_get_by_cidr',
+ self.fake_network_get_by_cidr)
+
+ def fake_network_delete_safe(context, network_id):
+ self.assertTrue(context.to_dict()['is_admin'])
+ self.assertEqual(network_id, self.fake_net['id'])
+ self.stubs.Set(db, 'network_delete_safe', fake_network_delete_safe)
+ self.commands.delete(fixed_range=self.fake_net['cidr'])
+
+ def _test_modify_base(self, update_value, project, host, dis_project=None,
+ dis_host=None):
+ self.fake_net = self.net
+ self.fake_update_value = update_value
+ self.stubs.Set(db, 'network_get_by_cidr',
+ self.fake_network_get_by_cidr)
+ self.stubs.Set(db, 'network_update', self.fake_network_update)
+ self.commands.modify(self.fake_net['cidr'], project=project, host=host,
+ dis_project=dis_project, dis_host=dis_host)
+
+ def test_modify_associate(self):
+ self._test_modify_base(update_value={'project_id': 'test_project',
+ 'host': 'test_host'},
+ project='test_project', host='test_host')
+
+ def test_modify_unchanged(self):
+ self._test_modify_base(update_value={}, project=None, host=None)
+
+ def test_modify_disassociate(self):
+ self._test_modify_base(update_value={'project_id': None, 'host': None},
+ project=None, host=None, dis_project=True,
+ dis_host=True)
diff --git a/nova/tests/test_test_utils.py b/nova/tests/test_test_utils.py
new file mode 100644
index 000000000..237339758
--- /dev/null
+++ b/nova/tests/test_test_utils.py
@@ -0,0 +1,41 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2010 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 nova import db
+from nova import test
+from nova.tests import utils as test_utils
+
+
+class TestUtilsTestCase(test.TestCase):
+ def test_get_test_admin_context(self):
+ """get_test_admin_context's return value behaves like admin context"""
+ ctxt = test_utils.get_test_admin_context()
+
+ # TODO(soren): This should verify the full interface context
+ # objects expose.
+ self.assertTrue(ctxt.is_admin)
+
+ def test_get_test_instance(self):
+ """get_test_instance's return value looks like an instance_ref"""
+ instance_ref = test_utils.get_test_instance()
+ ctxt = test_utils.get_test_admin_context()
+ db.instance_get(ctxt, instance_ref['id'])
+
+ def _test_get_test_network_info(self):
+ """Does the return value match a real network_info structure"""
+ # The challenge here is to define what exactly such a structure
+ # must look like.
+ pass
diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py
new file mode 100644
index 000000000..480247c91
--- /dev/null
+++ b/nova/tests/test_virt_drivers.py
@@ -0,0 +1,489 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2010 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.
+
+import base64
+import netaddr
+import sys
+import traceback
+
+from nova import exception
+from nova import flags
+from nova import image
+from nova import log as logging
+from nova import test
+from nova.tests import utils as test_utils
+
+libvirt = None
+FLAGS = flags.FLAGS
+
+LOG = logging.getLogger('nova.tests.test_virt_drivers')
+
+
+def catch_notimplementederror(f):
+ """Decorator to simplify catching drivers raising NotImplementedError
+
+ If a particular call makes a driver raise NotImplementedError, we
+ log it so that we can extract this information afterwards to
+ automatically generate a hypervisor/feature support matrix."""
+ def wrapped_func(self, *args, **kwargs):
+ try:
+ return f(self, *args, **kwargs)
+ except NotImplementedError:
+ frame = traceback.extract_tb(sys.exc_info()[2])[-1]
+ LOG.error('%(driver)s does not implement %(method)s' % {
+ 'driver': type(self.connection),
+ 'method': frame[2]})
+
+ wrapped_func.__name__ = f.__name__
+ wrapped_func.__doc__ = f.__doc__
+ return wrapped_func
+
+
+class _VirtDriverTestCase(test.TestCase):
+ def setUp(self):
+ super(_VirtDriverTestCase, self).setUp()
+ self.connection = self.driver_module.get_connection('')
+ self.ctxt = test_utils.get_test_admin_context()
+ self.image_service = image.get_default_image_service()
+
+ @catch_notimplementederror
+ def test_init_host(self):
+ self.connection.init_host('myhostname')
+
+ @catch_notimplementederror
+ def test_list_instances(self):
+ self.connection.list_instances()
+
+ @catch_notimplementederror
+ def test_list_instances_detail(self):
+ self.connection.list_instances_detail()
+
+ @catch_notimplementederror
+ def test_spawn(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+
+ domains = self.connection.list_instances()
+ self.assertIn(instance_ref['name'], domains)
+
+ domains_details = self.connection.list_instances_detail()
+ self.assertIn(instance_ref['name'], [i.name for i in domains_details])
+
+ @catch_notimplementederror
+ def test_snapshot_not_running(self):
+ instance_ref = test_utils.get_test_instance()
+ img_ref = self.image_service.create(self.ctxt, {'name': 'snap-1'})
+ self.assertRaises(exception.InstanceNotRunning,
+ self.connection.snapshot,
+ self.ctxt, instance_ref, img_ref['id'])
+
+ @catch_notimplementederror
+ def test_snapshot_running(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ img_ref = self.image_service.create(self.ctxt, {'name': 'snap-1'})
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.snapshot(self.ctxt, instance_ref, img_ref['id'])
+
+ @catch_notimplementederror
+ def test_reboot(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.reboot(instance_ref, network_info)
+
+ @catch_notimplementederror
+ def test_get_host_ip_addr(self):
+ host_ip = self.connection.get_host_ip_addr()
+
+ # Will raise an exception if it's not a valid IP at all
+ ip = netaddr.IPAddress(host_ip)
+
+ # For now, assume IPv4.
+ self.assertEquals(ip.version, 4)
+
+ @catch_notimplementederror
+ def test_resize_running(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.resize(instance_ref, 7)
+
+ @catch_notimplementederror
+ def test_set_admin_password(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.set_admin_password(instance_ref, 'p4ssw0rd')
+
+ @catch_notimplementederror
+ def test_inject_file(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.inject_file(instance_ref,
+ base64.b64encode('/testfile'),
+ base64.b64encode('testcontents'))
+
+ @catch_notimplementederror
+ def test_agent_update(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.agent_update(instance_ref, 'http://www.openstack.org/',
+ 'd41d8cd98f00b204e9800998ecf8427e')
+
+ @catch_notimplementederror
+ def test_rescue(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.rescue(self.ctxt, instance_ref,
+ lambda x: None, network_info)
+
+ @catch_notimplementederror
+ def test_unrescue_unrescued_instance(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.unrescue(instance_ref, lambda x: None, network_info)
+
+ @catch_notimplementederror
+ def test_unrescue_rescued_instance(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.rescue(self.ctxt, instance_ref,
+ lambda x: None, network_info)
+ self.connection.unrescue(instance_ref, lambda x: None, network_info)
+
+ @catch_notimplementederror
+ def test_poll_rescued_instances(self):
+ self.connection.poll_rescued_instances(10)
+
+ @catch_notimplementederror
+ def test_migrate_disk_and_power_off(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.migrate_disk_and_power_off(instance_ref, 'dest_host')
+
+ @catch_notimplementederror
+ def test_pause(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.pause(instance_ref, None)
+
+ @catch_notimplementederror
+ def test_unpause_unpaused_instance(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.unpause(instance_ref, None)
+
+ @catch_notimplementederror
+ def test_unpause_paused_instance(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.pause(instance_ref, None)
+ self.connection.unpause(instance_ref, None)
+
+ @catch_notimplementederror
+ def test_suspend(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.suspend(instance_ref, None)
+
+ @catch_notimplementederror
+ def test_resume_unsuspended_instance(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.resume(instance_ref, None)
+
+ @catch_notimplementederror
+ def test_resume_suspended_instance(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.suspend(instance_ref, None)
+ self.connection.resume(instance_ref, None)
+
+ @catch_notimplementederror
+ def test_destroy_instance_nonexistant(self):
+ fake_instance = {'id': 42, 'name': 'I just made this up!'}
+ network_info = test_utils.get_test_network_info()
+ self.connection.destroy(fake_instance, network_info)
+
+ @catch_notimplementederror
+ def test_destroy_instance(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.assertIn(instance_ref['name'],
+ self.connection.list_instances())
+ self.connection.destroy(instance_ref, network_info)
+ self.assertNotIn(instance_ref['name'],
+ self.connection.list_instances())
+
+ @catch_notimplementederror
+ def test_attach_detach_volume(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.attach_volume(instance_ref['name'],
+ '/dev/null', '/mnt/nova/something')
+ self.connection.detach_volume(instance_ref['name'],
+ '/mnt/nova/something')
+
+ @catch_notimplementederror
+ def test_get_info(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ info = self.connection.get_info(instance_ref['name'])
+ self.assertIn('state', info)
+ self.assertIn('max_mem', info)
+ self.assertIn('mem', info)
+ self.assertIn('num_cpu', info)
+ self.assertIn('cpu_time', info)
+
+ @catch_notimplementederror
+ def test_get_info_for_unknown_instance(self):
+ self.assertRaises(exception.NotFound,
+ self.connection.get_info, 'I just made this name up')
+
+ @catch_notimplementederror
+ def test_get_diagnostics(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.get_diagnostics(instance_ref['name'])
+
+ @catch_notimplementederror
+ def test_list_disks(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.list_disks(instance_ref['name'])
+
+ @catch_notimplementederror
+ def test_list_interfaces(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.list_interfaces(instance_ref['name'])
+
+ @catch_notimplementederror
+ def test_block_stats(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ stats = self.connection.block_stats(instance_ref['name'], 'someid')
+ self.assertEquals(len(stats), 5)
+
+ @catch_notimplementederror
+ def test_interface_stats(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ stats = self.connection.interface_stats(instance_ref['name'], 'someid')
+ self.assertEquals(len(stats), 8)
+
+ @catch_notimplementederror
+ def test_get_console_output(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ console_output = self.connection.get_console_output(instance_ref)
+ self.assertTrue(isinstance(console_output, basestring))
+
+ @catch_notimplementederror
+ def test_get_ajax_console(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ ajax_console = self.connection.get_ajax_console(instance_ref)
+ self.assertIn('token', ajax_console)
+ self.assertIn('host', ajax_console)
+ self.assertIn('port', ajax_console)
+
+ @catch_notimplementederror
+ def test_get_vnc_console(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ vnc_console = self.connection.get_vnc_console(instance_ref)
+ self.assertIn('token', vnc_console)
+ self.assertIn('host', vnc_console)
+ self.assertIn('port', vnc_console)
+
+ @catch_notimplementederror
+ def test_get_console_pool_info(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ console_pool = self.connection.get_console_pool_info(instance_ref)
+ self.assertIn('address', console_pool)
+ self.assertIn('username', console_pool)
+ self.assertIn('password', console_pool)
+
+ @catch_notimplementederror
+ def test_refresh_security_group_rules(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ # FIXME: Create security group and add the instance to it
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.refresh_security_group_rules(1)
+
+ @catch_notimplementederror
+ def test_refresh_security_group_members(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ # FIXME: Create security group and add the instance to it
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.refresh_security_group_members(1)
+
+ @catch_notimplementederror
+ def test_refresh_provider_fw_rules(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.refresh_provider_fw_rules()
+
+ @catch_notimplementederror
+ def test_update_available_resource(self):
+ self.compute = self.start_service('compute', host='dummy')
+ self.connection.update_available_resource(self.ctxt, 'dummy')
+
+ @catch_notimplementederror
+ def test_compare_cpu(self):
+ cpu_info = '''{ "topology": {
+ "sockets": 1,
+ "cores": 2,
+ "threads": 1 },
+ "features": [
+ "xtpr",
+ "tm2",
+ "est",
+ "vmx",
+ "ds_cpl",
+ "monitor",
+ "pbe",
+ "tm",
+ "ht",
+ "ss",
+ "acpi",
+ "ds",
+ "vme"],
+ "arch": "x86_64",
+ "model": "Penryn",
+ "vendor": "Intel" }'''
+
+ self.connection.compare_cpu(cpu_info)
+
+ @catch_notimplementederror
+ def test_ensure_filtering_for_instance(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.ensure_filtering_rules_for_instance(instance_ref,
+ network_info)
+
+ @catch_notimplementederror
+ def test_unfilter_instance(self):
+ instance_ref = test_utils.get_test_instance()
+ network_info = test_utils.get_test_network_info()
+ self.connection.unfilter_instance(instance_ref, network_info)
+
+ @catch_notimplementederror
+ def test_live_migration(self):
+ network_info = test_utils.get_test_network_info()
+ instance_ref = test_utils.get_test_instance()
+ self.connection.spawn(self.ctxt, instance_ref, network_info)
+ self.connection.live_migration(self.ctxt, instance_ref, 'otherhost',
+ None, None)
+
+ @catch_notimplementederror
+ def _check_host_status_fields(self, host_status):
+ self.assertIn('host_name-description', host_status)
+ self.assertIn('host_hostname', host_status)
+ self.assertIn('host_memory_total', host_status)
+ self.assertIn('host_memory_overhead', host_status)
+ self.assertIn('host_memory_free', host_status)
+ self.assertIn('host_memory_free_computed', host_status)
+ self.assertIn('host_other_config', host_status)
+ self.assertIn('host_ip_address', host_status)
+ self.assertIn('host_cpu_info', host_status)
+ self.assertIn('disk_available', host_status)
+ self.assertIn('disk_total', host_status)
+ self.assertIn('disk_used', host_status)
+ self.assertIn('host_uuid', host_status)
+ self.assertIn('host_name_label', host_status)
+
+ @catch_notimplementederror
+ def test_update_host_status(self):
+ host_status = self.connection.update_host_status()
+ self._check_host_status_fields(host_status)
+
+ @catch_notimplementederror
+ def test_get_host_stats(self):
+ host_status = self.connection.get_host_stats()
+ self._check_host_status_fields(host_status)
+
+ @catch_notimplementederror
+ def test_set_host_enabled(self):
+ self.connection.set_host_enabled('a useless argument?', True)
+
+ @catch_notimplementederror
+ def test_host_power_action_reboot(self):
+ self.connection.host_power_action('a useless argument?', 'reboot')
+
+ @catch_notimplementederror
+ def test_host_power_action_shutdown(self):
+ self.connection.host_power_action('a useless argument?', 'shutdown')
+
+ @catch_notimplementederror
+ def test_host_power_action_startup(self):
+ self.connection.host_power_action('a useless argument?', 'startup')
+
+
+class AbstractDriverTestCase(_VirtDriverTestCase):
+ def setUp(self):
+ import nova.virt.driver
+
+ self.driver_module = nova.virt.driver
+
+ def get_driver_connection(_):
+ return nova.virt.driver.ComputeDriver()
+
+ self.driver_module.get_connection = get_driver_connection
+ super(AbstractDriverTestCase, self).setUp()
+
+
+class FakeConnectionTestCase(_VirtDriverTestCase):
+ def setUp(self):
+ import nova.virt.fake
+ self.driver_module = nova.virt.fake
+ super(FakeConnectionTestCase, self).setUp()
+
+# Before long, we'll add the real hypervisor drivers here as well
+# with whatever instrumentation they need to work independently of
+# their hypervisor. This way, we can verify that they all act the
+# same.
diff --git a/nova/tests/utils.py b/nova/tests/utils.py
new file mode 100644
index 000000000..e0cacadb4
--- /dev/null
+++ b/nova/tests/utils.py
@@ -0,0 +1,68 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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
+#
+
+import nova.context
+import nova.db
+import nova.flags
+
+FLAGS = nova.flags.FLAGS
+
+
+def get_test_admin_context():
+ return nova.context.get_admin_context()
+
+
+def get_test_instance(context=None):
+ if not context:
+ context = get_test_admin_context()
+
+ test_instance = {'memory_kb': '1024000',
+ 'basepath': '/some/path',
+ 'bridge_name': 'br100',
+ 'vcpus': 2,
+ 'project_id': 'fake',
+ 'bridge': 'br101',
+ 'image_ref': '1',
+ 'instance_type_id': '5'} # m1.small
+
+ instance_ref = nova.db.instance_create(context, test_instance)
+ return instance_ref
+
+
+def get_test_network_info(count=1):
+ ipv6 = FLAGS.use_ipv6
+ fake = 'fake'
+ fake_ip = '0.0.0.0/0'
+ fake_ip_2 = '0.0.0.1/0'
+ fake_ip_3 = '0.0.0.1/0'
+ fake_vlan = 100
+ fake_bridge_interface = 'eth0'
+ network = {'bridge': fake,
+ 'cidr': fake_ip,
+ 'cidr_v6': fake_ip,
+ 'vlan': fake_vlan,
+ 'bridge_interface': fake_bridge_interface,
+ 'injected': False}
+ mapping = {'mac': fake,
+ 'dhcp_server': fake,
+ 'gateway': fake,
+ 'gateway6': fake,
+ 'ips': [{'ip': fake_ip}, {'ip': fake_ip}]}
+ if ipv6:
+ mapping['ip6s'] = [{'ip': fake_ip},
+ {'ip': fake_ip_2},
+ {'ip': fake_ip_3}]
+ return [(network, mapping) for x in xrange(0, count)]
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index 93290aba7..d05b51bd9 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -140,7 +140,7 @@ class ComputeDriver(object):
that it was before this call began.
:param context: security context
- :param instance: Instance of {nova.compute.service.Instance}.
+ :param instance: Instance object as returned by DB layer.
This function should use the data there to guide
the creation of the new instance.
:param network_info:
@@ -152,14 +152,11 @@ class ComputeDriver(object):
def destroy(self, instance, network_info, cleanup=True):
"""Destroy (shutdown and delete) the specified instance.
- The given parameter is an instance of nova.compute.service.Instance,
-
If the instance is not found (for example if networking failed), this
function should still succeed. It's probably a good idea to log a
warning in that case.
- :param instance: Instance of {nova.compute.service.Instance} and so
- the instance is being specified as instance.name.
+ :param instance: Instance object as returned by DB layer.
:param network_info:
:py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
:param cleanup:
@@ -171,8 +168,7 @@ class ComputeDriver(object):
def reboot(self, instance, network_info):
"""Reboot the specified instance.
- :param instance: Instance of {nova.compute.service.Instance} and so
- the instance is being specified as instance.name.
+ :param instance: Instance object as returned by DB layer.
:param network_info:
:py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
"""
@@ -240,10 +236,10 @@ class ComputeDriver(object):
"""
Snapshots the specified instance.
- The given parameter is an instance of nova.compute.service.Instance,
- and so the instance is being specified as instance.name.
-
- The second parameter is the name of the snapshot.
+ :param context: security context
+ :param instance: Instance object as returned by DB layer.
+ :param image_id: Reference to a pre-created image that will
+ hold the snapshot.
"""
raise NotImplementedError()
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 13b7aeab5..d5e2bf31b 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -67,6 +67,7 @@ class FakeConnection(driver.ComputeDriver):
'disk_used': 100000000000,
'host_uuid': 'cedb9b39-9388-41df-8891-c5c9a0c0fe5f',
'host_name_label': 'fake-mini'}
+ self._mounts = {}
@classmethod
def instance(cls):
@@ -99,7 +100,8 @@ class FakeConnection(driver.ComputeDriver):
self.instances[name] = fake_instance
def snapshot(self, context, instance, name):
- pass
+ if not instance['name'] in self.instances:
+ raise exception.InstanceNotRunning()
def reboot(self, instance, network_info):
pass
@@ -144,7 +146,7 @@ class FakeConnection(driver.ComputeDriver):
pass
def destroy(self, instance, network_info, cleanup=True):
- key = instance.name
+ key = instance['name']
if key in self.instances:
del self.instances[key]
else:
@@ -152,9 +154,16 @@ class FakeConnection(driver.ComputeDriver):
(key, self.instances))
def attach_volume(self, instance_name, device_path, mountpoint):
+ if not instance_name in self._mounts:
+ self._mounts[instance_name] = {}
+ self._mounts[instance_name][mountpoint] = device_path
return True
def detach_volume(self, instance_name, mountpoint):
+ try:
+ del self._mounts[instance_name][mountpoint]
+ except KeyError:
+ pass
return True
def get_info(self, instance_name):
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 64c106f47..c5f105f40 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -239,9 +239,8 @@ class VMOps(object):
self._attach_disks(instance, disk_image_type, vm_ref, first_vdi_ref,
vdis)
- # Alter the image before VM start for, e.g. network injection also
- # alter the image if there's metadata.
- if FLAGS.flat_injected or instance['metadata']:
+ # Alter the image before VM start for network injection.
+ if FLAGS.flat_injected:
VMHelper.preconfigure_instance(self._session, instance,
first_vdi_ref, network_info)
diff --git a/nova/volume/driver.py b/nova/volume/driver.py
index 2e9a394c7..7a02a7c14 100644
--- a/nova/volume/driver.py
+++ b/nova/volume/driver.py
@@ -530,7 +530,7 @@ class ISCSIDriver(VolumeDriver):
"node.session.auth.password",
iscsi_properties['auth_password'])
- self._run_iscsiadm(iscsi_properties, "--login")
+ self._run_iscsiadm(iscsi_properties, ("--login", ))
self._iscsiadm_update(iscsi_properties, "node.startup", "automatic")
@@ -551,7 +551,7 @@ class ISCSIDriver(VolumeDriver):
locals())
# The rescan isn't documented as being necessary(?), but it helps
- self._run_iscsiadm(iscsi_properties, "--rescan")
+ self._run_iscsiadm(iscsi_properties, ("--rescan", ))
tries = tries + 1
if not os.path.exists(mount_device):
@@ -568,7 +568,7 @@ class ISCSIDriver(VolumeDriver):
"""Undiscover volume on a remote host."""
iscsi_properties = self._get_iscsi_properties(volume)
self._iscsiadm_update(iscsi_properties, "node.startup", "manual")
- self._run_iscsiadm(iscsi_properties, "--logout")
+ self._run_iscsiadm(iscsi_properties, ("--logout", ))
self._run_iscsiadm(iscsi_properties, ('--op', 'delete'))
def check_for_export(self, context, volume_id):