diff --git a/nova/tests/virt/vmwareapi/stubs.py b/nova/tests/virt/vmwareapi/stubs.py index 93c55a4531b4..a061ff678203 100644 --- a/nova/tests/virt/vmwareapi/stubs.py +++ b/nova/tests/virt/vmwareapi/stubs.py @@ -65,10 +65,8 @@ def set_stubs(stubs): """Set the stubs.""" stubs.Set(network_util, 'get_network_with_the_name', fake.fake_get_network) - stubs.Set(vmware_images, 'fetch_image', fake.fake_fetch_image) stubs.Set(vmware_images, 'get_vmdk_size_and_properties', fake.fake_get_vmdk_size_and_properties) - stubs.Set(vmware_images, 'upload_image', fake.fake_upload_image) stubs.Set(driver.VMwareAPISession, "_get_vim_object", fake_get_vim_object) stubs.Set(driver.VMwareAPISession, "_is_vim_object", diff --git a/nova/tests/virt/vmwareapi/test_configdrive.py b/nova/tests/virt/vmwareapi/test_configdrive.py old mode 100644 new mode 100755 index cebda3cf1f45..01e24fef7e06 --- a/nova/tests/virt/vmwareapi/test_configdrive.py +++ b/nova/tests/virt/vmwareapi/test_configdrive.py @@ -13,11 +13,15 @@ # License for the specific language governing permissions and limitations # under the License. +import contextlib import copy import fixtures + +import mock import mox from nova import context +from nova.image import glance from nova import test import nova.tests.image.fake from nova.tests import utils @@ -25,6 +29,7 @@ from nova.tests.virt.vmwareapi import stubs from nova.virt import fake from nova.virt.vmwareapi import driver from nova.virt.vmwareapi import fake as vmwareapi_fake +from nova.virt.vmwareapi import read_write_util from nova.virt.vmwareapi import vm_util from nova.virt.vmwareapi import vmops from nova.virt.vmwareapi import vmware_images @@ -47,13 +52,10 @@ class ConfigDriveTestCase(test.NoDBTestCase): nova.tests.image.fake.stub_out_image_service(self.stubs) self.conn = driver.VMwareVCDriver(fake.FakeVirtAPI) self.network_info = utils.get_test_network_info() - self.image = { - 'id': 'c1c8ce3d-c2e0-4247-890c-ccf5cc1c004c', - 'disk_format': 'vmdk', - 'size': 512, - } + self.vim = vmwareapi_fake.FakeVim() self.node_name = '%s(%s)' % (self.conn.dict_mors.keys()[0], cluster_name) + image_ref = nova.tests.image.fake.get_valid_image_id() self.test_instance = {'node': 'test_url', 'vm_state': 'building', 'project_id': 'fake', @@ -68,7 +70,7 @@ class ConfigDriveTestCase(test.NoDBTestCase): 'flavor': 'm1.large', 'vcpus': 4, 'root_gb': 80, - 'image_ref': '1', + 'image_ref': image_ref, 'host': 'fake_host', 'task_state': 'scheduling', @@ -78,6 +80,15 @@ class ConfigDriveTestCase(test.NoDBTestCase): 'node': self.node_name, 'metadata': []} + (image_service, image_id) = glance.get_remote_image_service(context, + image_ref) + metadata = image_service.show(context, image_id) + self.image = { + 'id': image_ref, + 'disk_format': 'vmdk', + 'size': int(metadata['size']), + } + class FakeInstanceMetadata(object): def __init__(self, instance, content=None, extra_md=None): pass @@ -109,11 +120,43 @@ class ConfigDriveTestCase(test.NoDBTestCase): def _spawn_vm(self, injected_files=[], admin_password=None, block_device_info=None): - self.conn.spawn(self.context, self.instance, self.image, + + read_file_handle = mock.MagicMock() + write_file_handle = mock.MagicMock() + self.image_ref = self.instance['image_ref'] + + def fake_read_handle(read_iter): + return read_file_handle + + def fake_write_handle(host, dc_name, ds_name, cookies, + file_path, file_size, scheme="https"): + self.assertEqual('dc1', dc_name) + self.assertEqual('ds1', ds_name) + self.assertEqual('Fake-CookieJar', cookies) + split_file_path = file_path.split('/') + self.assertEqual('vmware_temp', split_file_path[0]) + self.assertEqual(self.image_ref, split_file_path[2]) + self.assertEqual(('%s-flat.vmdk' % self.image_ref), + split_file_path[3]) + self.assertEqual(int(self.image['size']), file_size) + + return write_file_handle + + with contextlib.nested( + mock.patch.object(read_write_util, 'VMwareHTTPWriteFile', + side_effect=fake_write_handle), + mock.patch.object(read_write_util, 'GlanceFileRead', + side_effect=fake_read_handle), + mock.patch.object(vmware_images, 'start_transfer') + ) as (fake_http_write, fake_glance_read, fake_start_transfer): + self.conn.spawn(self.context, self.instance, self.image, injected_files=injected_files, admin_password=admin_password, network_info=self.network_info, block_device_info=block_device_info) + fake_start_transfer.assert_called_once_with(self.context, + read_file_handle, self.image['size'], + write_file_handle=write_file_handle) def test_create_vm_with_config_drive_verify_method_invocation(self): self.instance = copy.deepcopy(self.test_instance) diff --git a/nova/tests/virt/vmwareapi/test_driver_api.py b/nova/tests/virt/vmwareapi/test_driver_api.py old mode 100644 new mode 100755 index 6910c6bf235e..31c7ad9c8bd1 --- a/nova/tests/virt/vmwareapi/test_driver_api.py +++ b/nova/tests/virt/vmwareapi/test_driver_api.py @@ -37,6 +37,7 @@ from nova.compute import task_states from nova.compute import vm_states from nova import context from nova import exception +from nova.image import glance from nova.openstack.common import jsonutils from nova.openstack.common import timeutils from nova.openstack.common import units @@ -56,6 +57,7 @@ from nova.virt.vmwareapi import ds_util from nova.virt.vmwareapi import error_util from nova.virt.vmwareapi import fake as vmwareapi_fake from nova.virt.vmwareapi import imagecache +from nova.virt.vmwareapi import read_write_util from nova.virt.vmwareapi import vim from nova.virt.vmwareapi import vim_util from nova.virt.vmwareapi import vm_util @@ -319,16 +321,23 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): self.context = context.RequestContext(self.user_id, self.project_id) stubs.set_stubs(self.stubs) vmwareapi_fake.reset() + nova.tests.image.fake.stub_out_image_service(self.stubs) self.conn = driver.VMwareESXDriver(fake.FakeVirtAPI) + self.vim = vmwareapi_fake.FakeVim() + # NOTE(vish): none of the network plugging code is actually # being tested self.network_info = utils.get_test_network_info() - + image_ref = nova.tests.image.fake.get_valid_image_id() + (image_service, image_id) = glance.get_remote_image_service( + self.context, image_ref) + metadata = image_service.show(self.context, image_id) self.image = { - 'id': 'c1c8ce3d-c2e0-4247-890c-ccf5cc1c004c', + 'id': image_ref, 'disk_format': 'vmdk', - 'size': 512, + 'size': int(metadata['size']), } + self.fake_image_uuid = self.image['id'] nova.tests.image.fake.stub_out_image_service(self.stubs) self.vnc_host = 'test_url' self._set_exception_vars() @@ -347,7 +356,7 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): 'instance_type': 'm1.large', 'vcpus': 4, 'root_gb': 80, - 'image_ref': '1', + 'image_ref': self.image['id'], 'host': 'fake_host', 'task_state': 'scheduling', @@ -453,7 +462,7 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): 'swap': self.type_data['swap'], } if set_image_ref: - values['image_ref'] = "fake_image_uuid" + values['image_ref'] = self.fake_image_uuid self.instance_node = node self.uuid = uuid self.instance = fake_instance.fake_instance_obj( @@ -462,17 +471,48 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): def _create_vm(self, node=None, num_instances=1, uuid=None, instance_type='m1.large', powered_on=True): """Create and spawn the VM.""" + + read_file_handle = mock.MagicMock() + write_file_handle = mock.MagicMock() + + def fake_read_handle(read_iter): + return read_file_handle + + def _fake_write_mock(host, dc_name, ds_name, cookies, + file_path, file_size, scheme="https"): + self.vim.fake_transfer_file(ds_name=ds_name, + file_path=file_path) + image_ref = self.image['id'] + self.assertIn(dc_name, ['dc1', 'dc2']) + self.assertIn(ds_name, ['ds1', 'ds2']) + self.assertEqual('Fake-CookieJar', cookies) + split_file_path = file_path.split('/') + self.assertEqual('vmware_temp', split_file_path[0]) + self.assertEqual(image_ref, split_file_path[2]) + self.assertEqual(int(self.image['size']), file_size) + return write_file_handle + if not node: node = self.node_name self._create_instance(node=node, uuid=uuid, instance_type=instance_type) self.assertIsNone(vm_util.vm_ref_cache_get(self.uuid)) - self.conn.spawn(self.context, self.instance, self.image, - injected_files=[], admin_password=None, - network_info=self.network_info, - block_device_info=None) - self._check_vm_record(num_instances=num_instances, - powered_on=powered_on) + with contextlib.nested( + mock.patch.object(read_write_util, 'VMwareHTTPWriteFile', + _fake_write_mock), + mock.patch.object(read_write_util, 'GlanceFileRead', + fake_read_handle), + mock.patch.object(vmware_images, 'start_transfer') + ) as (fake_http_write, fake_glance_read, fake_start_transfer): + self.conn.spawn(self.context, self.instance, self.image, + injected_files=[], admin_password=None, + network_info=self.network_info, + block_device_info=None) + fake_start_transfer.assert_called_once_with(self.context, + read_file_handle, self.image['size'], + write_file_handle=write_file_handle) + self._check_vm_record(num_instances=num_instances, + powered_on=powered_on) self.assertIsNotNone(vm_util.vm_ref_cache_get(self.uuid)) def _check_vm_record(self, num_instances=1, powered_on=True): @@ -560,8 +600,8 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): self.assertEqual(len(uuids), 0) def _cached_files_exist(self, exists=True): - cache = ('[%s] vmware_base/fake_image_uuid/fake_image_uuid.vmdk' % - self.ds) + cache = ('[%s] vmware_base/%s/%s.vmdk' % + (self.ds, self.fake_image_uuid, self.fake_image_uuid)) if exists: self.assertTrue(vmwareapi_fake.get_file(cache)) else: @@ -574,8 +614,8 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): self._create_vm() inst_file_path = '[%s] %s/%s.vmdk' % (self.ds, self.uuid, self.uuid) - cache = ('[%s] vmware_base/fake_image_uuid/fake_image_uuid.vmdk' % - self.ds) + cache = ('[%s] vmware_base/%s/%s.vmdk' % + (self.ds, self.fake_image_uuid, self.fake_image_uuid)) self.assertTrue(vmwareapi_fake.get_file(inst_file_path)) self._cached_files_exist() @@ -583,18 +623,19 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): """Test image disk is cached when use_linked_clone is True.""" self.flags(use_linked_clone=True, group='vmware') self._create_vm() - file = ('[%s] vmware_base/fake_image_uuid/fake_image_uuid.vmdk' % - self.ds) - root = ('[%s] vmware_base/fake_image_uuid/fake_image_uuid.80.vmdk' % - self.ds) + file = '[%s] vmware_base/%s/%s.vmdk' % (self.ds, self.fake_image_uuid, + self.fake_image_uuid) + root = '[%s] vmware_base/%s/%s.80.vmdk' % (self.ds, + self.fake_image_uuid, + self.fake_image_uuid) self.assertTrue(vmwareapi_fake.get_file(file)) self.assertTrue(vmwareapi_fake.get_file(root)) def _iso_disk_type_created(self, instance_type='m1.large'): self.image['disk_format'] = 'iso' self._create_vm(instance_type=instance_type) - file = ('[%s] vmware_base/fake_image_uuid/fake_image_uuid.iso' % - self.ds) + file = '[%s] vmware_base/%s/%s.iso' % (self.ds, self.fake_image_uuid, + self.fake_image_uuid) self.assertTrue(vmwareapi_fake.get_file(file)) def test_iso_disk_type_created(self): @@ -609,7 +650,8 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): def test_iso_disk_cdrom_attach(self): self.iso_path = ( - '[%s] vmware_base/fake_image_uuid/fake_image_uuid.iso' % self.ds) + '[%s] vmware_base/%s/%s.iso' % (self.ds, self.fake_image_uuid, + self.fake_image_uuid)) def fake_attach_cdrom(vm_ref, instance, data_store_ref, iso_uploaded_path): @@ -623,8 +665,8 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): def test_iso_disk_cdrom_attach_with_config_drive(self): self.flags(force_config_drive=True) self.iso_path = [ - ('[%s] vmware_base/fake_image_uuid/fake_image_uuid.iso' % - self.ds), + '[%s] vmware_base/%s/%s.iso' % + (self.ds, self.fake_image_uuid, self.fake_image_uuid), '[%s] fake-config-drive' % self.ds] self.iso_unit_nos = [0, 1] self.iso_index = 0 @@ -757,8 +799,8 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): self._check_vm_info(info, power_state.RUNNING) def test_spawn_disk_extend_exists(self): - root = ('[%s] vmware_base/fake_image_uuid/fake_image_uuid.80.vmdk' % - self.ds) + root = ('[%s] vmware_base/%s/%s.80.vmdk' % + (self.ds, self.fake_image_uuid, self.fake_image_uuid)) self.root = root def _fake_extend(instance, requested_size, name, dc_ref): @@ -796,7 +838,7 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): self.wait_task = self.conn._session._wait_for_task self.call_method = self.conn._session._call_method self.task_ref = None - id = 'fake_image_uuid' + id = self.fake_image_uuid cached_image = '[%s] vmware_base/%s/%s.80.vmdk' % (self.ds, id, id) tmp_file = '[%s] vmware_base/%s/%s.80-flat.vmdk' % (self.ds, @@ -1399,8 +1441,21 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): fake_power_on_instance) self.stubs.Set(self.conn._volumeops, "attach_disk_to_vm", fake_attach_disk_to_vm) - self.conn.rescue(self.context, self.instance, self.network_info, - self.image, None) + + def _fake_http_write(host, data_center_name, datastore_name, + cookies, file_path, file_size, scheme="https"): + self.vim.fake_transfer_file(ds_name=datastore_name, + file_path=file_path) + + with contextlib.nested( + mock.patch.object(read_write_util, 'VMwareHTTPWriteFile', + _fake_http_write), + mock.patch.object(read_write_util, 'GlanceFileRead'), + mock.patch.object(vmware_images, 'start_transfer') + + ) as (http_write, glance_read, fake_start_transfer): + self.conn.rescue(self.context, self.instance, self.network_info, + self.image, 'fake-password') info = self.conn.get_info({'name': '1-rescue', 'uuid': '%s-rescue' % self.uuid, 'node': self.instance_node}) @@ -1794,8 +1849,9 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): _fake_get_timestamp_filename) def _timestamp_file_exists(self, exists=True): - timestamp = ('[%s] vmware_base/fake_image_uuid/%s/' % - (self.ds, self._get_timestamp_filename())) + timestamp = ('[%s] vmware_base/%s/%s/' % + (self.ds, self.fake_image_uuid, + self._get_timestamp_filename())) if exists: self.assertTrue(vmwareapi_fake.get_file(timestamp)) else: @@ -1826,8 +1882,8 @@ class VMwareAPIVMTestCase(test.NoDBTestCase): def test_timestamp_file_removed_aging(self): self._timestamp_file_removed() ts = self._get_timestamp_filename() - ts_path = ('[%s] vmware_base/fake_image_uuid/%s/' % - (self.ds, ts)) + ts_path = ('[%s] vmware_base/%s/%s/' % + (self.ds, self.fake_image_uuid, ts)) vmwareapi_fake._add_file(ts_path) self._timestamp_file_exists() all_instances = [self.instance] diff --git a/nova/tests/virt/vmwareapi/test_vmware_images.py b/nova/tests/virt/vmwareapi/test_vmware_images.py new file mode 100755 index 000000000000..98fb7694a710 --- /dev/null +++ b/nova/tests/virt/vmwareapi/test_vmware_images.py @@ -0,0 +1,95 @@ +# Copyright (c) 2014 VMware, Inc. +# 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. + +""" +Test suite for vmware_images. +""" + +import contextlib + +import mock + +from nova import test +import nova.tests.image.fake +from nova.virt.vmwareapi import read_write_util +from nova.virt.vmwareapi import vmware_images + + +class VMwareImagesTestCase(test.NoDBTestCase): + """Unit tests for Vmware API connection calls.""" + + def setUp(self): + super(VMwareImagesTestCase, self).setUp() + + def tearDown(self): + super(VMwareImagesTestCase, self).tearDown() + + def test_fetch_image(self): + """Test fetching images.""" + + dc_name = 'fake-dc' + file_path = 'fake_file' + ds_name = 'ds1' + host = mock.MagicMock() + context = mock.MagicMock() + + image_data = { + 'id': nova.tests.image.fake.get_valid_image_id(), + 'disk_format': 'vmdk', + 'size': 512, + } + read_file_handle = mock.MagicMock() + write_file_handle = mock.MagicMock() + read_iter = mock.MagicMock() + instance = {} + instance['image_ref'] = image_data['id'] + instance['uuid'] = 'fake-uuid' + + def fake_read_handle(read_iter): + return read_file_handle + + def fake_write_handle(host, dc_name, ds_name, cookies, + file_path, file_size): + return write_file_handle + + def fake_download(context, image_id): + return read_iter + + def fake_image_show(context, image_id): + return image_data + + with contextlib.nested( + mock.patch.object(read_write_util, 'GlanceFileRead', + side_effect=fake_read_handle), + mock.patch.object(read_write_util, 'VMwareHTTPWriteFile', + side_effect=fake_write_handle), + mock.patch.object(vmware_images, 'start_transfer'), + mock.patch.object(nova.image.glance.GlanceImageService, 'show', + side_effect=fake_image_show), + mock.patch.object(nova.image.glance.GlanceImageService, + 'download', side_effect=fake_download), + ) as (glance_read, http_write, start_transfer, image_show, + image_download): + vmware_images.fetch_image(context, instance, + host, dc_name, + ds_name, file_path) + + glance_read.assert_called_once_with(read_iter) + http_write.assert_called_once_with(host, dc_name, ds_name, None, + file_path, image_data['size']) + start_transfer.assert_called_once_with( + context, read_file_handle, + image_data['size'], + write_file_handle=write_file_handle) + image_download.assert_called_once_with(context, instance['image_ref']) + image_show.assert_called_once_with(context, instance['image_ref']) diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py index 6cabfcb6d55f..cfada93f56f3 100644 --- a/nova/virt/vmwareapi/fake.py +++ b/nova/virt/vmwareapi/fake.py @@ -946,14 +946,6 @@ def get_file(file_path): return file_path in _db_content.get("files") -def fake_fetch_image(context, image, instance, **kwargs): - """Fakes fetch image call. Just adds a reference to the db for the file.""" - ds_name = kwargs.get("datastore_name") - file_path = kwargs.get("file_path") - ds_file_path = "[" + ds_name + "] " + file_path - _add_file(ds_file_path) - - def fake_upload_image(context, image, instance, **kwargs): """Fakes the upload of an image.""" pass @@ -1272,6 +1264,13 @@ class FakeVim(object): task_mdo = create_task(method, "success") return task_mdo.obj + def fake_transfer_file(self, ds_name, file_path): + """Fakes fetch image call. + Just adds a reference to the db for the file. + """ + ds_file_path = "[" + ds_name + "] " + file_path + _add_file(ds_file_path) + def _make_dir(self, method, *args, **kwargs): """Creates a directory in the datastore.""" ds_path = kwargs.get("name") diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py old mode 100644 new mode 100755 index d1777f47ed99..844363360c48 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -278,30 +278,6 @@ class VMwareVMOps(object): vnc_port = vm_util.get_vnc_port(self._session) self._set_vnc_config(client_factory, instance, vnc_port) - def _fetch_image_on_datastore(upload_name): - """Fetch image from Glance to datastore.""" - LOG.debug(_("Downloading image file data %(image_ref)s to the " - "data store %(data_store_name)s") % - {'image_ref': instance['image_ref'], - 'data_store_name': data_store_name}, - instance=instance) - vmware_images.fetch_image( - context, - instance['image_ref'], - instance, - host=self._session._host_ip, - data_center_name=dc_info.name, - datastore_name=data_store_name, - cookies=cookies, - file_path=upload_name) - LOG.debug(_("Downloaded image file data %(image_ref)s to " - "%(upload_name)s on the data store " - "%(data_store_name)s") % - {'image_ref': instance['image_ref'], - 'upload_name': upload_name, - 'data_store_name': data_store_name}, - instance=instance) - def _copy_virtual_disk(source, dest): """Copy a sparse virtual disk to a thin virtual disk.""" # Copy a sparse virtual disk to a thin virtual disk. This is also @@ -431,7 +407,13 @@ class VMwareVMOps(object): else: upload_file_name = sparse_uploaded_vmdk_name - _fetch_image_on_datastore(upload_file_name) + vmware_images.fetch_image(context, + instance, + self._session._host_ip, + dc_info.name, + data_store_name, + upload_file_name, + cookies=cookies) if not is_iso and disk_type == "sparse": # Copy the sparse virtual disk to a thin virtual disk. diff --git a/nova/virt/vmwareapi/vmware_images.py b/nova/virt/vmwareapi/vmware_images.py index 2ae050ce22da..17e8cb2d12c7 100644 --- a/nova/virt/vmwareapi/vmware_images.py +++ b/nova/virt/vmwareapi/vmware_images.py @@ -113,25 +113,32 @@ def upload_iso_to_datastore(iso_path, instance, **kwargs): instance=instance) -def fetch_image(context, image, instance, **kwargs): +def fetch_image(context, instance, host, dc_name, ds_name, file_path, + cookies=None): """Download image from the glance image server.""" - LOG.debug(_("Downloading image %s from glance image server") % image, + image_ref = instance['image_ref'] + LOG.debug("Downloading image file data %(image_ref)s to the " + "data store %(data_store_name)s", + {'image_ref': image_ref, + 'data_store_name': ds_name}, instance=instance) - (image_service, image_id) = glance.get_remote_image_service(context, image) + + (image_service, image_id) = glance.get_remote_image_service(context, + image_ref) metadata = image_service.show(context, image_id) file_size = int(metadata['size']) read_iter = image_service.download(context, image_id) read_file_handle = read_write_util.GlanceFileRead(read_iter) write_file_handle = read_write_util.VMwareHTTPWriteFile( - kwargs.get("host"), - kwargs.get("data_center_name"), - kwargs.get("datastore_name"), - kwargs.get("cookies"), - kwargs.get("file_path"), - file_size) + host, dc_name, ds_name, cookies, file_path, file_size) start_transfer(context, read_file_handle, file_size, write_file_handle=write_file_handle) - LOG.debug(_("Downloaded image %s from glance image server") % image, + LOG.debug("Downloaded image file data %(image_ref)s to " + "%(upload_name)s on the data store " + "%(data_store_name)s", + {'image_ref': image_ref, + 'upload_name': 'n/a' if file_path is None else file_path, + 'data_store_name': 'n/a' if ds_name is None else ds_name}, instance=instance)