summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ironic/cmd/dbsync.py8
-rw-r--r--ironic/common/exception.py5
-rw-r--r--ironic/common/images.py16
-rw-r--r--ironic/db/migration.py4
-rw-r--r--ironic/db/sqlalchemy/migration.py26
-rw-r--r--ironic/drivers/modules/drac/client.py28
-rw-r--r--ironic/drivers/modules/drac/common.py11
-rw-r--r--ironic/drivers/modules/drac/management.py26
-rw-r--r--ironic/drivers/modules/drac/power.py8
-rw-r--r--ironic/drivers/modules/image_cache.py72
-rw-r--r--ironic/tests/db/sqlalchemy/test_migrations.py28
-rw-r--r--ironic/tests/drivers/drac/test_client.py39
-rw-r--r--ironic/tests/drivers/drac/test_management.py48
-rw-r--r--ironic/tests/drivers/drac/test_power.py27
-rw-r--r--ironic/tests/drivers/drac/utils.py14
-rw-r--r--ironic/tests/drivers/test_image_cache.py37
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')