diff options
-rw-r--r-- | ironic/cmd/dbsync.py | 8 | ||||
-rw-r--r-- | ironic/common/exception.py | 5 | ||||
-rw-r--r-- | ironic/common/images.py | 16 | ||||
-rw-r--r-- | ironic/db/migration.py | 4 | ||||
-rw-r--r-- | ironic/db/sqlalchemy/migration.py | 26 | ||||
-rw-r--r-- | ironic/drivers/modules/drac/client.py | 28 | ||||
-rw-r--r-- | ironic/drivers/modules/drac/common.py | 11 | ||||
-rw-r--r-- | ironic/drivers/modules/drac/management.py | 26 | ||||
-rw-r--r-- | ironic/drivers/modules/drac/power.py | 8 | ||||
-rw-r--r-- | ironic/drivers/modules/image_cache.py | 72 | ||||
-rw-r--r-- | ironic/tests/db/sqlalchemy/test_migrations.py | 28 | ||||
-rw-r--r-- | ironic/tests/drivers/drac/test_client.py | 39 | ||||
-rw-r--r-- | ironic/tests/drivers/drac/test_management.py | 48 | ||||
-rw-r--r-- | ironic/tests/drivers/drac/test_power.py | 27 | ||||
-rw-r--r-- | ironic/tests/drivers/drac/utils.py | 14 | ||||
-rw-r--r-- | ironic/tests/drivers/test_image_cache.py | 37 |
16 files changed, 263 insertions, 134 deletions
diff --git a/ironic/cmd/dbsync.py b/ironic/cmd/dbsync.py index d0d6ba6f0..57059ba11 100644 --- a/ironic/cmd/dbsync.py +++ b/ironic/cmd/dbsync.py @@ -47,6 +47,9 @@ class DBCommand(object): def version(self): print(migration.version()) + def create_schema(self): + migration.create_schema() + def add_command_parsers(subparsers): command_object = DBCommand() @@ -71,6 +74,9 @@ def add_command_parsers(subparsers): parser = subparsers.add_parser('version') parser.set_defaults(func=command_object.version) + parser = subparsers.add_parser('create_schema') + parser.set_defaults(func=command_object.create_schema) + command_opt = cfg.SubCommandOpt('command', title='Command', @@ -85,7 +91,7 @@ def main(): # pls change it to ironic-dbsync upgrade valid_commands = set([ 'upgrade', 'downgrade', 'revision', - 'version', 'stamp' + 'version', 'stamp', 'create_schema', ]) if not set(sys.argv) & valid_commands: sys.argv.append('upgrade') diff --git a/ironic/common/exception.py b/ironic/common/exception.py index c60d46577..dbe22d41d 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -440,6 +440,11 @@ class DracConfigJobCreationError(DracOperationError): 'Reason: %(error)s') +class DracInvalidFilterDialect(DracOperationError): + message = _('Invalid filter dialect \'%(invalid_filter)s\'. ' + 'Supported options are %(supported)s') + + class FailedToGetSensorData(IronicException): message = _("Failed to get sensor data for node %(node)s. " "Error: %(error)s") diff --git a/ironic/common/images.py b/ironic/common/images.py index bceea1f11..37f2dce93 100644 --- a/ironic/common/images.py +++ b/ironic/common/images.py @@ -292,3 +292,19 @@ def download_size(context, image_href, image_service=None): if not image_service: image_service = service.Service(version=1, context=context) return image_service.show(image_href)['size'] + + +def converted_size(path): + """Get size of converted raw image. + + The size of image converted to raw format can be growing up to the virtual + size of the image. + + :param path: path to the image file. + :returns: virtual size of the image or 0 if conversion not needed. + + """ + data = qemu_img_info(path) + if data.file_format == "raw" or not CONF.force_raw_images: + return 0 + return data.virtual_size diff --git a/ironic/db/migration.py b/ironic/db/migration.py index 35f19d942..846a60e24 100644 --- a/ironic/db/migration.py +++ b/ironic/db/migration.py @@ -45,3 +45,7 @@ def stamp(version): def revision(message, autogenerate): return IMPL.revision(message, autogenerate) + + +def create_schema(): + return IMPL.create_schema() diff --git a/ironic/db/sqlalchemy/migration.py b/ironic/db/sqlalchemy/migration.py index 6ebb949f7..60e3f53b9 100644 --- a/ironic/db/sqlalchemy/migration.py +++ b/ironic/db/sqlalchemy/migration.py @@ -19,8 +19,10 @@ import os import alembic from alembic import config as alembic_config import alembic.migration as alembic_migration +from oslo.db import exception as db_exc from ironic.db.sqlalchemy import api as sqla_api +from ironic.db.sqlalchemy import models def _alembic_config(): @@ -29,13 +31,14 @@ def _alembic_config(): return config -def version(config=None): +def version(config=None, engine=None): """Current database version. :returns: Database version :rtype: string """ - engine = sqla_api.get_engine() + if engine is None: + engine = sqla_api.get_engine() with engine.connect() as conn: context = alembic_migration.MigrationContext.configure(conn) return context.get_current_revision() @@ -53,6 +56,25 @@ def upgrade(revision, config=None): alembic.command.upgrade(config, revision or 'head') +def create_schema(config=None, engine=None): + """Create database schema from models description. + + Can be used for initial installation instead of upgrade('head'). + """ + if engine is None: + engine = sqla_api.get_engine() + + # NOTE(viktors): If we will use metadata.create_all() for non empty db + # schema, it will only add the new tables, but leave + # existing as is. So we should avoid of this situation. + if version(engine=engine) is not None: + raise db_exc.DbMigrationError("DB schema is already under version" + " control. Use upgrade() instead") + + models.Base.metadata.create_all(engine) + stamp('head', config=config) + + def downgrade(revision, config=None): """Used for downgrading database. diff --git a/ironic/drivers/modules/drac/client.py b/ironic/drivers/modules/drac/client.py index 99b052db6..73cfe13cf 100644 --- a/ironic/drivers/modules/drac/client.py +++ b/ironic/drivers/modules/drac/client.py @@ -24,6 +24,11 @@ pywsman = importutils.try_import('pywsman') _SOAP_ENVELOPE_URI = 'http://www.w3.org/2003/05/soap-envelope' +# Filter Dialects, see (Section 2.3.1): +# http://en.community.dell.com/techcenter/extras/m/white_papers/20439105.aspx +_FILTER_DIALECT_MAP = {'cql': 'http://schemas.dmtf.org/wbem/cql/1/dsp0202.pdf', + 'wql': 'http://schemas.microsoft.com/wbem/wsman/1/WQL'} + class Client(object): @@ -37,19 +42,36 @@ class Client(object): self.client = pywsman_client - def wsman_enumerate(self, resource_uri, options, filter=None): + def wsman_enumerate(self, resource_uri, options, filter_query=None, + filter_dialect='cql'): """Enumerates a remote WS-Man class. :param resource_uri: URI of the resource. :param options: client options. - :param filter: filter for enumeration. + :param filter_query: the query string. + :param filter_dialect: the filter dialect. Valid options are: + 'cql' and 'wql'. Defaults to 'cql'. :raises: DracClientError on an error from pywsman library. + :raises: DracInvalidFilterDialect if an invalid filter dialect + was specified. :returns: an ElementTree object of the response received. """ + filter_ = None + if filter_query is not None: + try: + filter_dialect = _FILTER_DIALECT_MAP[filter_dialect] + except KeyError: + valid_opts = ', '.join(_FILTER_DIALECT_MAP) + raise exception.DracInvalidFilterDialect( + invalid_filter=filter_dialect, supported=valid_opts) + + filter_ = pywsman.Filter() + filter_.simple(filter_dialect, filter_query) + options.set_flags(pywsman.FLAG_ENUMERATION_OPTIMIZATION) options.set_max_elements(100) - doc = self.client.enumerate(options, filter, resource_uri) + doc = self.client.enumerate(options, filter_, resource_uri) root = self._get_root(doc) final_xml = root diff --git a/ironic/drivers/modules/drac/common.py b/ironic/drivers/modules/drac/common.py index 9413ed078..dddf68bbc 100644 --- a/ironic/drivers/modules/drac/common.py +++ b/ironic/drivers/modules/drac/common.py @@ -37,6 +37,11 @@ OPTIONAL_PROPERTIES = { COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) +# ReturnValue constants +RET_SUCCESS = '0' +RET_ERROR = '2' +RET_CREATED = '4096' + def parse_driver_info(node): """Parses the driver_info of the node, reads default values @@ -109,8 +114,10 @@ def find_xml(doc, item, namespace, find_all=False): :param namespace: the namespace of the element. :param find_all: Boolean value, if True find all elements, if False find only the first one. Defaults to False. - :returns: The element object if find_all is False or a list of - element objects if find_all is True. + :returns: if find_all is False the element object will be returned + if found, None if not found. If find_all is True a list of + element objects will be returned or an empty list if no + elements were found. """ query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace, diff --git a/ironic/drivers/modules/drac/management.py b/ironic/drivers/modules/drac/management.py index ede5bf4df..4598f078b 100644 --- a/ironic/drivers/modules/drac/management.py +++ b/ironic/drivers/modules/drac/management.py @@ -50,13 +50,6 @@ NOT_NEXT = '2' # is not the next boot config the system will use ONE_TIME_BOOT = '3' # is the next boot config the system will use, # one time boot only -# ReturnValue constants -RET_SUCCESS = '0' -RET_ERROR = '2' -RET_CREATED = '4096' - -FILTER_DIALECT = 'http://schemas.dmtf.org/wbem/cql/1/dsp0202.pdf' - def _get_next_boot_mode(node): """Get the next boot mode. @@ -75,14 +68,11 @@ def _get_next_boot_mode(node): """ client = drac_common.get_wsman_client(node) options = pywsman.ClientOptions() - filter = pywsman.Filter() filter_query = ('select * from DCIM_BootConfigSetting where IsNext=%s ' 'or IsNext=%s' % (PERSISTENT, ONE_TIME_BOOT)) - filter.simple(FILTER_DIALECT, filter_query) - try: doc = client.wsman_enumerate(resource_uris.DCIM_BootConfigSetting, - options, filter) + options, filter_query=filter_query) except exception.DracClientError as exc: with excutils.save_and_reraise_exception(): LOG.error(_LE('DRAC driver failed to get next boot mode for ' @@ -138,7 +128,7 @@ def _create_config_job(node): # or RET_CREATED job created (but changes will be # applied after the reboot) # Boot Management Documentation: http://goo.gl/aEsvUH (Section 8.4) - if return_value == RET_ERROR: + if return_value == drac_common.RET_ERROR: error_message = drac_common.find_xml(doc, 'Message', resource_uris.DCIM_BIOSService).text raise exception.DracConfigJobCreationError(error=error_message) @@ -234,15 +224,12 @@ class DracManagement(base.ManagementInterface): client = drac_common.get_wsman_client(task.node) options = pywsman.ClientOptions() - filter = pywsman.Filter() filter_query = ("select * from DCIM_BootSourceSetting where " "InstanceID like '%%#%s%%'" % _BOOT_DEVICES_MAP[device]) - filter.simple(FILTER_DIALECT, filter_query) - try: doc = client.wsman_enumerate(resource_uris.DCIM_BootSourceSetting, - options, filter) + options, filter_query=filter_query) except exception.DracClientError as exc: with excutils.save_and_reraise_exception(): LOG.error(_LE('DRAC driver failed to set the boot device ' @@ -274,7 +261,7 @@ class DracManagement(base.ManagementInterface): # created (but changes will be applied after # the reboot) # Boot Management Documentation: http://goo.gl/aEsvUH (Section 8.7) - if return_value == RET_ERROR: + if return_value == drac_common.RET_ERROR: error_message = drac_common.find_xml(doc, 'Message', resource_uris.DCIM_BootConfigSetting).text raise exception.DracOperationError(operation='set_boot_device', @@ -304,15 +291,12 @@ class DracManagement(base.ManagementInterface): instance_id = boot_mode['instance_id'] options = pywsman.ClientOptions() - filter = pywsman.Filter() filter_query = ('select * from DCIM_BootSourceSetting where ' 'PendingAssignedSequence=0 and ' 'BootSourceType="%s"' % instance_id) - filter.simple(FILTER_DIALECT, filter_query) - try: doc = client.wsman_enumerate(resource_uris.DCIM_BootSourceSetting, - options, filter) + options, filter_query=filter_query) except exception.DracClientError as exc: with excutils.save_and_reraise_exception(): LOG.error(_LE('DRAC driver failed to get the current boot ' diff --git a/ironic/drivers/modules/drac/power.py b/ironic/drivers/modules/drac/power.py index 2f48aff9c..b53281724 100644 --- a/ironic/drivers/modules/drac/power.py +++ b/ironic/drivers/modules/drac/power.py @@ -51,15 +51,11 @@ def _get_power_state(node): client = drac_common.get_wsman_client(node) options = pywsman.ClientOptions() - filter = pywsman.Filter() - filter_dialect = 'http://schemas.dmtf.org/wbem/cql/1/dsp0202.pdf' filter_query = ('select EnabledState,ElementName from CIM_ComputerSystem ' 'where Name="srv:system"') - filter.simple(filter_dialect, filter_query) - try: doc = client.wsman_enumerate(resource_uris.DCIM_ComputerSystem, - options, filter) + options, filter_query=filter_query) except exception.DracClientError as exc: with excutils.save_and_reraise_exception(): LOG.error(_LE('DRAC driver failed to get power state for node ' @@ -100,7 +96,7 @@ def _set_power_state(node, target_state): return_value = drac_common.find_xml(root, 'ReturnValue', resource_uris.DCIM_ComputerSystem).text - if return_value != '0': + if return_value != drac_common.RET_SUCCESS: message = drac_common.find_xml(root, 'Message', resource_uris.DCIM_ComputerSystem).text LOG.error(_LE('DRAC driver failed to set power state for node ' diff --git a/ironic/drivers/modules/image_cache.py b/ironic/drivers/modules/image_cache.py index c125948ea..d7f0c714e 100644 --- a/ironic/drivers/modules/image_cache.py +++ b/ironic/drivers/modules/image_cache.py @@ -87,11 +87,9 @@ class ImageCache(object): #NOTE(ghe): We don't share images between instances/hosts if not CONF.parallel_image_downloads: with lockutils.lock(img_download_lock_name, 'ironic-'): - images.fetch_to_raw(ctx, uuid, dest_path, - self._image_service) + _fetch_to_raw(ctx, uuid, dest_path, self._image_service) else: - images.fetch_to_raw(ctx, uuid, dest_path, - self._image_service) + _fetch_to_raw(ctx, uuid, dest_path, self._image_service) return #TODO(ghe): have hard links and counts the same behaviour in all fs @@ -143,8 +141,7 @@ class ImageCache(object): tmp_dir = tempfile.mkdtemp(dir=self.master_dir) tmp_path = os.path.join(tmp_dir, uuid) try: - images.fetch_to_raw(ctx, uuid, tmp_path, - self._image_service) + _fetch_to_raw(ctx, uuid, tmp_path, self._image_service) # NOTE(dtantsur): no need for global lock here - master_path # will have link count >1 at any moment, so won't be cleaned up os.link(tmp_path, master_path) @@ -283,6 +280,47 @@ def _free_disk_space_for(path): return stat.f_frsize * stat.f_bavail +def _fetch_to_raw(context, image_href, path, image_service=None): + """Fetch image and convert to raw format if needed.""" + path_tmp = "%s.part" % path + images.fetch(context, image_href, path_tmp, image_service) + required_space = images.converted_size(path_tmp) + directory = os.path.dirname(path_tmp) + _clean_up_caches(directory, required_space) + images.image_to_raw(image_href, path, path_tmp) + + +def _clean_up_caches(directory, amount): + """Explicitly cleanup caches based on their priority (if required). + + :param directory: the directory (of the cache) to be freed up. + :param amount: amount of space to reclaim. + :raises: InsufficientDiskSpace exception, if we cannot free up enough space + after trying all the caches. + """ + free = _free_disk_space_for(directory) + + if amount < free: + return + + # NOTE(dtantsur): filter caches, whose directory is on the same device + st_dev = os.stat(directory).st_dev + + caches_to_clean = [x[1]() for x in _cache_cleanup_list] + caches = (c for c in caches_to_clean + if os.stat(c.master_dir).st_dev == st_dev) + for cache_to_clean in caches: + cache_to_clean.clean_up(amount=(amount - free)) + free = _free_disk_space_for(directory) + if amount < free: + break + else: + raise exception.InsufficientDiskSpace(path=directory, + required=amount / 1024 / 1024, + actual=free / 1024 / 1024, + ) + + def clean_up_caches(ctx, directory, images_info): """Explicitly cleanup caches based on their priority (if required). @@ -300,27 +338,7 @@ def clean_up_caches(ctx, directory, images_info): """ total_size = sum(images.download_size(ctx, uuid) for (uuid, path) in images_info) - free = _free_disk_space_for(directory) - - if total_size >= free: - # NOTE(dtantsur): filter caches, whose directory is on the same device - st_dev = os.stat(directory).st_dev - - caches_to_clean = [x[1]() for x in _cache_cleanup_list] - caches = (c for c in caches_to_clean - if os.stat(c.master_dir).st_dev == st_dev) - for cache_to_clean in caches: - # NOTE(dtantsur): multiplying by 2 is an attempt to account for - # images converting to raw format - cache_to_clean.clean_up(amount=(2 * total_size - free)) - free = _free_disk_space_for(directory) - if total_size < free: - break - else: - raise exception.InsufficientDiskSpace(path=directory, - required=total_size / 1024 / 1024, - actual=free / 1024 / 1024, - ) + _clean_up_caches(directory, total_size) def cleanup(priority): diff --git a/ironic/tests/db/sqlalchemy/test_migrations.py b/ironic/tests/db/sqlalchemy/test_migrations.py index 1c2533593..c6350f49c 100644 --- a/ironic/tests/db/sqlalchemy/test_migrations.py +++ b/ironic/tests/db/sqlalchemy/test_migrations.py @@ -42,7 +42,7 @@ import contextlib from alembic import script import mock -from oslo.db import exception +from oslo.db import exception as db_exc from oslo.db.sqlalchemy import test_base from oslo.db.sqlalchemy import test_migrations from oslo.db.sqlalchemy import utils as db_utils @@ -315,9 +315,33 @@ class MigrationCheckersMixin(object): # Ironic will use oslo.db 0.4.0 or higher. # See bug #1214341 for details. self.assertRaises( - (sqlalchemy.exc.IntegrityError, exception.DBDuplicateEntry), + (sqlalchemy.exc.IntegrityError, db_exc.DBDuplicateEntry), nodes.insert().execute, data) + def test_upgrade_and_version(self): + with patch_with_engine(self.engine): + self.migration_api.upgrade('head') + self.assertIsNotNone(self.migration_api.version()) + + def test_create_schema_and_version(self): + with patch_with_engine(self.engine): + self.migration_api.create_schema() + self.assertIsNotNone(self.migration_api.version()) + + def test_upgrade_and_create_schema(self): + with patch_with_engine(self.engine): + self.migration_api.upgrade('31baaf680d2b') + self.assertRaises(db_exc.DbMigrationError, + self.migration_api.create_schema) + + def test_upgrade_twice(self): + with patch_with_engine(self.engine): + self.migration_api.upgrade('31baaf680d2b') + v1 = self.migration_api.version() + self.migration_api.upgrade('head') + v2 = self.migration_api.version() + self.assertNotEqual(v1, v2) + class TestMigrationsMySQL(MigrationCheckersMixin, WalkVersionsMixin, diff --git a/ironic/tests/drivers/drac/test_client.py b/ironic/tests/drivers/drac/test_client.py index b4c1ac83b..e6d8e165e 100644 --- a/ironic/tests/drivers/drac/test_client.py +++ b/ironic/tests/drivers/drac/test_client.py @@ -18,6 +18,7 @@ Test class for DRAC client wrapper. import mock from xml.etree import ElementTree +from ironic.common import exception from ironic.drivers.modules.drac import client as drac_client from ironic.tests import base from ironic.tests.db import utils as db_utils @@ -30,12 +31,7 @@ INFO_DICT = db_utils.get_test_drac_info() class DracClientTestCase(base.TestCase): def test_wsman_enumerate(self, mock_client_pywsman): - mock_root = mock.Mock() - mock_root.string.return_value = '<test></test>' - mock_xml = mock.Mock() - mock_xml.root.return_value = mock_root - mock_xml.context.return_value = None - + mock_xml = test_utils.mock_wsman_root('<test></test>') mock_pywsman_client = mock_client_pywsman.Client.return_value mock_pywsman_client.enumerate.return_value = mock_xml @@ -81,12 +77,33 @@ class DracClientTestCase(base.TestCase): mock_pywsman_client.enumerate.assert_called_once_with(mock_options, None, resource_uri) - def test_wsman_invoke(self, mock_client_pywsman): - mock_root = mock.Mock() - mock_root.string.return_value = '<test></test>' - mock_xml = mock.Mock() - mock_xml.root.return_value = mock_root + def test_wsman_enumerate_filter_query(self, mock_client_pywsman): + mock_xml = test_utils.mock_wsman_root('<test></test>') + mock_pywsman_client = mock_client_pywsman.Client.return_value + mock_pywsman_client.enumerate.return_value = mock_xml + resource_uri = 'https://foo/wsman' + mock_options = mock_client_pywsman.ClientOptions.return_value + mock_filter = mock_client_pywsman.Filter.return_value + client = drac_client.Client(**INFO_DICT) + filter_query = 'SELECT * FROM foo' + client.wsman_enumerate(resource_uri, mock_options, + filter_query=filter_query) + + mock_filter.simple.assert_called_once_with(mock.ANY, filter_query) + mock_pywsman_client.enumerate.assert_called_once_with(mock_options, + mock_filter, resource_uri) + mock_xml.context.assert_called_once_with() + + def test_wsman_enumerate_invalid_filter_dialect(self, mock_client_pywsman): + client = drac_client.Client(**INFO_DICT) + self.assertRaises(exception.DracInvalidFilterDialect, + client.wsman_enumerate, 'https://foo/wsman', + mock.Mock(), filter_query='foo', + filter_dialect='invalid') + + def test_wsman_invoke(self, mock_client_pywsman): + mock_xml = test_utils.mock_wsman_root('<test></test>') mock_pywsman_client = mock_client_pywsman.Client.return_value mock_pywsman_client.invoke.return_value = mock_xml diff --git a/ironic/tests/drivers/drac/test_management.py b/ironic/tests/drivers/drac/test_management.py index e5a7ef1f8..a45214465 100644 --- a/ironic/tests/drivers/drac/test_management.py +++ b/ironic/tests/drivers/drac/test_management.py @@ -37,18 +37,6 @@ from ironic.tests.objects import utils as obj_utils INFO_DICT = db_utils.get_test_drac_info() -def _mock_wsman_root(return_value): - """Helper function to mock the root() from wsman client.""" - mock_xml_root = mock.Mock() - mock_xml_root.string.return_value = return_value - - mock_xml = mock.Mock() - mock_xml.context.return_value = None - mock_xml.root.return_value = mock_xml_root - - return mock_xml - - @mock.patch.object(drac_client, 'pywsman') class DracManagementInternalMethodsTestCase(base.TestCase): @@ -67,7 +55,7 @@ class DracManagementInternalMethodsTestCase(base.TestCase): drac_mgmt.PERSISTENT}}], resource_uris.DCIM_BootConfigSetting) - mock_xml = _mock_wsman_root(result_xml) + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.enumerate.return_value = mock_xml @@ -89,7 +77,7 @@ class DracManagementInternalMethodsTestCase(base.TestCase): drac_mgmt.ONE_TIME_BOOT}}], resource_uris.DCIM_BootConfigSetting) - mock_xml = _mock_wsman_root(result_xml) + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.enumerate.return_value = mock_xml @@ -106,7 +94,7 @@ class DracManagementInternalMethodsTestCase(base.TestCase): {'Name': 'fake'}}], resource_uris.DCIM_LifecycleJob) - mock_xml = _mock_wsman_root(result_xml) + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.enumerate.return_value = mock_xml @@ -123,7 +111,7 @@ class DracManagementInternalMethodsTestCase(base.TestCase): 'InstanceID': 'fake'}}], resource_uris.DCIM_LifecycleJob) - mock_xml = _mock_wsman_root(result_xml) + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.enumerate.return_value = mock_xml @@ -134,10 +122,10 @@ class DracManagementInternalMethodsTestCase(base.TestCase): def test__create_config_job(self, mock_client_pywsman): result_xml = test_utils.build_soap_xml([{'ReturnValue': - drac_mgmt.RET_SUCCESS}], + drac_common.RET_SUCCESS}], resource_uris.DCIM_BIOSService) - mock_xml = _mock_wsman_root(result_xml) + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.invoke.return_value = mock_xml @@ -149,11 +137,11 @@ class DracManagementInternalMethodsTestCase(base.TestCase): def test__create_config_job_error(self, mock_client_pywsman): result_xml = test_utils.build_soap_xml([{'ReturnValue': - drac_mgmt.RET_ERROR, + drac_common.RET_ERROR, 'Message': 'E_FAKE'}], resource_uris.DCIM_BIOSService) - mock_xml = _mock_wsman_root(result_xml) + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.invoke.return_value = mock_xml @@ -194,7 +182,7 @@ class DracManagementTestCase(base.TestCase): result_xml = test_utils.build_soap_xml([{'InstanceID': 'HardDisk'}], resource_uris.DCIM_BootSourceSetting) - mock_xml = _mock_wsman_root(result_xml) + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.enumerate.return_value = mock_xml @@ -213,7 +201,7 @@ class DracManagementTestCase(base.TestCase): result_xml = test_utils.build_soap_xml([{'InstanceID': 'NIC'}], resource_uris.DCIM_BootSourceSetting) - mock_xml = _mock_wsman_root(result_xml) + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.enumerate.return_value = mock_xml @@ -235,7 +223,7 @@ class DracManagementTestCase(base.TestCase): self.assertRaises(exception.DracClientError, self.driver.get_boot_device, self.task) mock_we.assert_called_once_with(resource_uris.DCIM_BootSourceSetting, - mock.ANY, mock.ANY) + mock.ANY, filter_query=mock.ANY) @mock.patch.object(drac_mgmt, '_check_for_config_job') @mock.patch.object(drac_mgmt, '_create_config_job') @@ -243,11 +231,11 @@ class DracManagementTestCase(base.TestCase): result_xml_enum = test_utils.build_soap_xml([{'InstanceID': 'NIC'}], resource_uris.DCIM_BootSourceSetting) result_xml_invk = test_utils.build_soap_xml([{'ReturnValue': - drac_mgmt.RET_SUCCESS}], + drac_common.RET_SUCCESS}], resource_uris.DCIM_BootConfigSetting) - mock_xml_enum = _mock_wsman_root(result_xml_enum) - mock_xml_invk = _mock_wsman_root(result_xml_invk) + mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum) + mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.enumerate.return_value = mock_xml_enum mock_pywsman.invoke.return_value = mock_xml_invk @@ -270,12 +258,12 @@ class DracManagementTestCase(base.TestCase): result_xml_enum = test_utils.build_soap_xml([{'InstanceID': 'NIC'}], resource_uris.DCIM_BootSourceSetting) result_xml_invk = test_utils.build_soap_xml([{'ReturnValue': - drac_mgmt.RET_ERROR, + drac_common.RET_ERROR, 'Message': 'E_FAKE'}], resource_uris.DCIM_BootConfigSetting) - mock_xml_enum = _mock_wsman_root(result_xml_enum) - mock_xml_invk = _mock_wsman_root(result_xml_invk) + mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum) + mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk) mock_pywsman = mock_client_pywsman.Client.return_value mock_pywsman.enumerate.return_value = mock_xml_enum mock_pywsman.invoke.return_value = mock_xml_invk @@ -302,7 +290,7 @@ class DracManagementTestCase(base.TestCase): self.driver.set_boot_device, self.task, boot_devices.PXE) mock_we.assert_called_once_with(resource_uris.DCIM_BootSourceSetting, - mock.ANY, mock.ANY) + mock.ANY, filter_query=mock.ANY) def test_get_sensors_data(self, mock_client_pywsman): self.assertRaises(NotImplementedError, diff --git a/ironic/tests/drivers/drac/test_power.py b/ironic/tests/drivers/drac/test_power.py index 9d97cf084..7b8699a42 100644 --- a/ironic/tests/drivers/drac/test_power.py +++ b/ironic/tests/drivers/drac/test_power.py @@ -48,13 +48,7 @@ class DracPowerInternalMethodsTestCase(base.TestCase): def test__get_power_state(self, mock_power_pywsman, mock_client_pywsman): result_xml = test_utils.build_soap_xml([{'EnabledState': '2'}], resource_uris.DCIM_ComputerSystem) - mock_xml_root = mock.Mock() - mock_xml_root.string.return_value = result_xml - - mock_xml = mock.Mock() - mock_xml.context.return_value = None - mock_xml.root.return_value = mock_xml_root - + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman_client = mock_client_pywsman.Client.return_value mock_pywsman_client.enumerate.return_value = mock_xml @@ -65,14 +59,10 @@ class DracPowerInternalMethodsTestCase(base.TestCase): mock.ANY, resource_uris.DCIM_ComputerSystem) def test__set_power_state(self, mock_power_pywsman, mock_client_pywsman): - result_xml = test_utils.build_soap_xml([{'ReturnValue': '0'}], + result_xml = test_utils.build_soap_xml([{'ReturnValue': + drac_common.RET_SUCCESS}], resource_uris.DCIM_ComputerSystem) - mock_xml_root = mock.Mock() - mock_xml_root.string.return_value = result_xml - - mock_xml = mock.Mock() - mock_xml.root.return_value = mock_xml_root - + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman_client = mock_client_pywsman.Client.return_value mock_pywsman_client.invoke.return_value = mock_xml @@ -92,15 +82,12 @@ class DracPowerInternalMethodsTestCase(base.TestCase): def test__set_power_state_fail(self, mock_power_pywsman, mock_client_pywsman): - result_xml = test_utils.build_soap_xml([{'ReturnValue': '1', + result_xml = test_utils.build_soap_xml([{'ReturnValue': + drac_common.RET_ERROR, 'Message': 'error message'}], resource_uris.DCIM_ComputerSystem) - mock_xml_root = mock.Mock() - mock_xml_root.string.return_value = result_xml - - mock_xml = mock.Mock() - mock_xml.root.return_value = mock_xml_root + mock_xml = test_utils.mock_wsman_root(result_xml) mock_pywsman_client = mock_client_pywsman.Client.return_value mock_pywsman_client.invoke.return_value = mock_xml diff --git a/ironic/tests/drivers/drac/utils.py b/ironic/tests/drivers/drac/utils.py index 2d2100dd3..c31bc2807 100644 --- a/ironic/tests/drivers/drac/utils.py +++ b/ironic/tests/drivers/drac/utils.py @@ -17,6 +17,8 @@ from xml.etree import ElementTree +import mock + def build_soap_xml(items, namespace=None): """Build a SOAP XML. @@ -56,3 +58,15 @@ def build_soap_xml(items, namespace=None): envelope_element.append(body_element) return ElementTree.tostring(envelope_element) + + +def mock_wsman_root(return_value): + """Helper function to mock the root() from wsman client.""" + mock_xml_root = mock.Mock() + mock_xml_root.string.return_value = return_value + + mock_xml = mock.Mock() + mock_xml.context.return_value = None + mock_xml.root.return_value = mock_xml_root + + return mock_xml diff --git a/ironic/tests/drivers/test_image_cache.py b/ironic/tests/drivers/test_image_cache.py index 2dd1aa855..0849b8285 100644 --- a/ironic/tests/drivers/test_image_cache.py +++ b/ironic/tests/drivers/test_image_cache.py @@ -33,7 +33,7 @@ def touch(filename): open(filename, 'w').close() -@mock.patch.object(images, 'fetch_to_raw') +@mock.patch.object(image_cache, '_fetch_to_raw') class TestImageCacheFetch(base.TestCase): def setUp(self): @@ -241,7 +241,7 @@ class TestImageCacheCleanUp(base.TestCase): mock_clean_ttl.assert_called_once_with(mock.ANY, None) @mock.patch.object(utils, 'rmtree_without_raise') - @mock.patch.object(images, 'fetch_to_raw') + @mock.patch.object(image_cache, '_fetch_to_raw') def test_temp_images_not_cleaned(self, mock_fetch_to_raw, mock_rmtree): def _fake_fetch_to_raw(ctx, uuid, tmp_path, *args): with open(tmp_path, 'w') as fp: @@ -258,7 +258,7 @@ class TestImageCacheCleanUp(base.TestCase): self.assertTrue(mock_rmtree.called) @mock.patch.object(utils, 'rmtree_without_raise') - @mock.patch.object(images, 'fetch_to_raw') + @mock.patch.object(image_cache, '_fetch_to_raw') def test_temp_dir_exception(self, mock_fetch_to_raw, mock_rmtree): mock_fetch_to_raw.side_effect = exception.IronicException self.assertRaises(exception.IronicException, @@ -355,7 +355,7 @@ class CleanupImageCacheTestCase(base.TestCase): mock_statvfs.assert_called_with('master_dir') self.assertEqual(2, mock_statvfs.call_count) self.mock_first_cache.return_value.clean_up.assert_called_once_with( - amount=(42 * 2 - 1)) + amount=(42 - 1)) self.assertFalse(self.mock_second_cache.return_value.clean_up.called) # Since we are using generator expression in clean_up_caches, stat on @@ -389,7 +389,7 @@ class CleanupImageCacheTestCase(base.TestCase): mock_statvfs.assert_called_with('master_dir') self.assertEqual(2, mock_statvfs.call_count) self.mock_second_cache.return_value.clean_up.assert_called_once_with( - amount=(42 * 2 - 1)) + amount=(42 - 1)) self.assertFalse(self.mock_first_cache.return_value.clean_up.called) # Since first cache exists on a different partition, it wouldn't be @@ -422,9 +422,9 @@ class CleanupImageCacheTestCase(base.TestCase): mock_statvfs.assert_called_with('master_dir') self.assertEqual(3, mock_statvfs.call_count) self.mock_first_cache.return_value.clean_up.assert_called_once_with( - amount=(42 * 2 - 1)) + amount=(42 - 1)) self.mock_second_cache.return_value.clean_up.assert_called_once_with( - amount=(42 * 2 - 2)) + amount=(42 - 2)) mock_stat_calls_expected = [mock.call('master_dir'), mock.call('first_cache_dir'), @@ -453,9 +453,9 @@ class CleanupImageCacheTestCase(base.TestCase): mock_statvfs.assert_called_with('master_dir') self.assertEqual(3, mock_statvfs.call_count) self.mock_first_cache.return_value.clean_up.assert_called_once_with( - amount=(42 * 2 - 1)) + amount=(42 - 1)) self.mock_second_cache.return_value.clean_up.assert_called_once_with( - amount=(42 * 2 - 1)) + amount=(42 - 1)) mock_stat_calls_expected = [mock.call('master_dir'), mock.call('first_cache_dir'), @@ -465,3 +465,22 @@ class CleanupImageCacheTestCase(base.TestCase): mock.call('master_dir')] self.assertEqual(mock_stat_calls_expected, mock_stat.mock_calls) self.assertEqual(mock_statvfs_calls_expected, mock_statvfs.mock_calls) + + +class TestFetchCleanup(base.TestCase): + + def setUp(self): + super(TestFetchCleanup, self).setUp() + + @mock.patch.object(images, 'converted_size') + @mock.patch.object(images, 'fetch') + @mock.patch.object(images, 'image_to_raw') + @mock.patch.object(image_cache, '_clean_up_caches') + def test__fetch_to_raw(self, mock_clean, mock_raw, mock_fetch, mock_size): + mock_size.return_value = 100 + image_cache._fetch_to_raw('fake', 'fake-uuid', '/foo/bar') + mock_fetch.assert_called_once_with('fake', 'fake-uuid', + '/foo/bar.part', None) + mock_clean.assert_called_once_with('/foo', 100) + mock_raw.assert_called_once_with('fake-uuid', '/foo/bar', + '/foo/bar.part') |