Add benchmark tests for glance
The patch adds the following benchmark tests for glance: * Add and delete image * Boot the several instances from added image Also some changes are added related with using fake Glance client the for following unit tests: * test_generic_cleanup in tests/benchmark/test_runner.py * test_snapshot_server in tests/benchmark/scenarios/nova/test_servers.py Implements: blueprint benchmark-scenarios Change-Id: I31471a91455bd20eea000a66c59320178455cb50
This commit is contained in:
parent
7bd24f74e7
commit
23971d6b7f
11
doc/samples/tasks/glance/create-and-delete-image.json
Normal file
11
doc/samples/tasks/glance/create-and-delete-image.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
"GlanceImages.create_and_delete_image": [
|
||||||
|
{"args": {"image_url": "http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-disk.img",
|
||||||
|
"container_format": "bare",
|
||||||
|
"disk_format": "qcow2"},
|
||||||
|
"execution": "continuous",
|
||||||
|
"config": {"times": 1, "active_users": 1,
|
||||||
|
"tenants": 1, "users_per_tenant": 1}}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"GlanceImages.create_image_and_boot_instances": [
|
||||||
|
{"args": {"image_url": "http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-disk.img",
|
||||||
|
"container_format": "bare",
|
||||||
|
"disk_format": "qcow2",
|
||||||
|
"flavor_id": 42,
|
||||||
|
"number_instances": 2},
|
||||||
|
"execution": "continuous",
|
||||||
|
"config": {"times": 1, "active_users": 1,
|
||||||
|
"tenants": 1, "users_per_tenant": 1}}
|
||||||
|
]
|
||||||
|
}
|
0
rally/benchmark/scenarios/glance/__init__.py
Normal file
0
rally/benchmark/scenarios/glance/__init__.py
Normal file
48
rally/benchmark/scenarios/glance/images.py
Normal file
48
rally/benchmark/scenarios/glance/images.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Copyright 2014: Mirantis Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from rally.benchmark.scenarios.glance import utils
|
||||||
|
from rally.benchmark.scenarios.nova import utils as nova_utils
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceImages(utils.GlanceScenario, nova_utils.NovaScenario):
|
||||||
|
|
||||||
|
def create_and_delete_image(self, container_format,
|
||||||
|
image_url, disk_format, **kwargs):
|
||||||
|
"""Test adds and then deletes image."""
|
||||||
|
image_name = self._generate_random_name(16)
|
||||||
|
image = self._create_image(image_name,
|
||||||
|
container_format,
|
||||||
|
image_url,
|
||||||
|
disk_format,
|
||||||
|
**kwargs)
|
||||||
|
self._delete_image(image)
|
||||||
|
|
||||||
|
def create_image_and_boot_instances(self, container_format,
|
||||||
|
image_url, disk_format,
|
||||||
|
flavor_id, number_instances,
|
||||||
|
**kwargs):
|
||||||
|
"""Test adds image, boots instance from it and then deletes them."""
|
||||||
|
image_name = self._generate_random_name(16)
|
||||||
|
image = self._create_image(image_name,
|
||||||
|
container_format,
|
||||||
|
image_url,
|
||||||
|
disk_format,
|
||||||
|
**kwargs)
|
||||||
|
image_id = image.id
|
||||||
|
server_name = self._generate_random_name(16)
|
||||||
|
self._boot_servers(server_name, image_id,
|
||||||
|
flavor_id, number_instances, **kwargs)
|
68
rally/benchmark/scenarios/glance/utils.py
Normal file
68
rally/benchmark/scenarios/glance/utils.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Copyright 2014: Mirantis Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
|
||||||
|
from rally.benchmark import base
|
||||||
|
from rally.benchmark import utils as bench_utils
|
||||||
|
from rally import utils
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceScenario(base.Scenario):
|
||||||
|
|
||||||
|
def _create_image(self, image_name, container_format,
|
||||||
|
image_url, disk_format, **kwargs):
|
||||||
|
"""Create a new image.
|
||||||
|
|
||||||
|
:param image_name: String used to name the image
|
||||||
|
:param container_format: Container format of image.
|
||||||
|
Acceptable formats: ami, ari, aki, bare, and ovf.
|
||||||
|
:param image_url: URL for download image
|
||||||
|
:param disk_format: Disk format of image. Acceptable formats:
|
||||||
|
ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso.
|
||||||
|
:param **kwargs: optional parameters to create image
|
||||||
|
|
||||||
|
returns: object of image
|
||||||
|
"""
|
||||||
|
image = self.clients("glance").images.create(
|
||||||
|
name=image_name,
|
||||||
|
copy_from=image_url,
|
||||||
|
container_format=container_format,
|
||||||
|
disk_format=disk_format,
|
||||||
|
**kwargs)
|
||||||
|
time.sleep(5)
|
||||||
|
image = utils.wait_for(image,
|
||||||
|
is_ready=bench_utils.resource_is("active"),
|
||||||
|
update_resource=bench_utils.get_from_manager(),
|
||||||
|
timeout=120, check_interval=3)
|
||||||
|
return image
|
||||||
|
|
||||||
|
def _delete_image(self, image):
|
||||||
|
"""Deletes the given image.
|
||||||
|
|
||||||
|
Returns when the image is actually deleted.
|
||||||
|
|
||||||
|
:param image: Image object
|
||||||
|
"""
|
||||||
|
image.delete()
|
||||||
|
utils.wait_for_delete(image,
|
||||||
|
update_resource=bench_utils.get_from_manager(),
|
||||||
|
timeout=120, check_interval=3)
|
||||||
|
|
||||||
|
def _generate_random_name(self, length):
|
||||||
|
name = ''.join(random.choice(string.lowercase) for i in range(length))
|
||||||
|
return 'test-rally-image' + name
|
0
tests/benchmark/scenarios/glance/__init__.py
Normal file
0
tests/benchmark/scenarios/glance/__init__.py
Normal file
80
tests/benchmark/scenarios/glance/test_images.py
Normal file
80
tests/benchmark/scenarios/glance/test_images.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# Copyright 2014: Mirantis Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from rally.benchmark.scenarios.glance import images
|
||||||
|
from rally.benchmark.scenarios.nova import servers
|
||||||
|
from rally.benchmark import utils as butils
|
||||||
|
from tests import fakes
|
||||||
|
from tests import test
|
||||||
|
|
||||||
|
GLANCE_IMAGES = "rally.benchmark.scenarios.glance.images.GlanceImages"
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceImagesTestCase(test.TestCase):
|
||||||
|
|
||||||
|
@mock.patch(GLANCE_IMAGES + "._generate_random_name")
|
||||||
|
@mock.patch(GLANCE_IMAGES + "._delete_image")
|
||||||
|
@mock.patch(GLANCE_IMAGES + "._create_image")
|
||||||
|
def test_create_and_delete_image(self, mock_create, mock_delete,
|
||||||
|
mock_random_name):
|
||||||
|
glance_scenario = images.GlanceImages()
|
||||||
|
fake_image = object()
|
||||||
|
mock_create.return_value = fake_image
|
||||||
|
mock_random_name.return_value = "test-rally-image"
|
||||||
|
glance_scenario.create_and_delete_image("cf", "url", "df",
|
||||||
|
fakearg="f")
|
||||||
|
|
||||||
|
mock_create.assert_called_once_with("test-rally-image", "cf",
|
||||||
|
"url", "df", fakearg="f")
|
||||||
|
mock_delete.assert_called_once_with(fake_image)
|
||||||
|
|
||||||
|
@mock.patch(GLANCE_IMAGES + "._generate_random_name")
|
||||||
|
@mock.patch(GLANCE_IMAGES + "._boot_servers")
|
||||||
|
@mock.patch(GLANCE_IMAGES + "._create_image")
|
||||||
|
@mock.patch("rally.benchmark.utils.osclients")
|
||||||
|
def test_create_image_and_boot_instances(self,
|
||||||
|
mock_osclients,
|
||||||
|
mock_create_image,
|
||||||
|
mock_boot_servers,
|
||||||
|
mock_random_name):
|
||||||
|
glance_scenario = images.GlanceImages()
|
||||||
|
nova_scenario = servers.NovaServers()
|
||||||
|
fc = fakes.FakeClients()
|
||||||
|
mock_osclients.Clients.return_value = fc
|
||||||
|
fake_glance = fakes.FakeGlanceClient()
|
||||||
|
fc.get_glance_client = lambda: fake_glance
|
||||||
|
fake_nova = fakes.FakeNovaClient()
|
||||||
|
fc.get_nova_client = lambda: fake_nova
|
||||||
|
temp_keys = ["username", "password", "tenant_name", "uri"]
|
||||||
|
users_endpoints = [dict(zip(temp_keys, temp_keys))]
|
||||||
|
nova_scenario._clients = butils.create_openstack_clients(
|
||||||
|
users_endpoints, temp_keys)[0]
|
||||||
|
fake_image = fakes.FakeImage()
|
||||||
|
fake_servers = [object() for i in range(5)]
|
||||||
|
mock_create_image.return_value = fake_image
|
||||||
|
mock_boot_servers.return_value = fake_servers
|
||||||
|
mock_random_name.return_value = "random_name"
|
||||||
|
kwargs = {'fakearg': 'f'}
|
||||||
|
with mock.patch("rally.benchmark.scenarios.glance.utils.time.sleep"):
|
||||||
|
glance_scenario.\
|
||||||
|
create_image_and_boot_instances("cf", "url", "df",
|
||||||
|
"fid", 5, **kwargs)
|
||||||
|
mock_create_image.assert_called_once_with("random_name", "cf",
|
||||||
|
"url", "df", **kwargs)
|
||||||
|
mock_boot_servers.assert_called_once_with("random_name",
|
||||||
|
"image-id-0",
|
||||||
|
"fid", 5, **kwargs)
|
81
tests/benchmark/scenarios/glance/test_utils.py
Normal file
81
tests/benchmark/scenarios/glance/test_utils.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from rally.benchmark.scenarios.glance import utils
|
||||||
|
from rally.benchmark import utils as butils
|
||||||
|
from rally import exceptions as rally_exceptions
|
||||||
|
from rally.openstack.common.fixture import mockpatch
|
||||||
|
from tests import fakes
|
||||||
|
from tests import test
|
||||||
|
|
||||||
|
BM_UTILS = 'rally.benchmark.utils'
|
||||||
|
GLANCE_UTILS = 'rally.benchmark.scenarios.glance.utils'
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceScenarioTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(GlanceScenarioTestCase, self).setUp()
|
||||||
|
self.image = mock.Mock()
|
||||||
|
self.image1 = mock.Mock()
|
||||||
|
self.res_is = mockpatch.Patch(BM_UTILS + ".resource_is")
|
||||||
|
self.get_fm = mockpatch.Patch(BM_UTILS + '.get_from_manager')
|
||||||
|
self.wait_for = mockpatch.Patch(GLANCE_UTILS + ".utils.wait_for")
|
||||||
|
self.wait_for_delete = mockpatch.Patch(
|
||||||
|
GLANCE_UTILS + ".utils.wait_for_delete")
|
||||||
|
self.useFixture(self.wait_for)
|
||||||
|
self.useFixture(self.wait_for_delete)
|
||||||
|
self.useFixture(self.res_is)
|
||||||
|
self.useFixture(self.get_fm)
|
||||||
|
self.gfm = self.get_fm.mock
|
||||||
|
self.useFixture(mockpatch.Patch('time.sleep'))
|
||||||
|
self.scenario = utils.GlanceScenario()
|
||||||
|
|
||||||
|
def test_generate_random_name(self):
|
||||||
|
for length in [8, 16, 32, 64]:
|
||||||
|
name = self.scenario._generate_random_name(length)
|
||||||
|
self.assertEqual(len(name), 16 + length)
|
||||||
|
|
||||||
|
def test_failed_image_status(self):
|
||||||
|
self.get_fm.cleanUp()
|
||||||
|
image_manager = fakes.FakeFailedImageManager()
|
||||||
|
self.assertRaises(rally_exceptions.GetResourceFailure,
|
||||||
|
butils.get_from_manager(),
|
||||||
|
image_manager.create('fails', 'url', 'cf', 'df'))
|
||||||
|
|
||||||
|
@mock.patch(GLANCE_UTILS + '.GlanceScenario.clients')
|
||||||
|
def test__create_image(self, mock_clients):
|
||||||
|
mock_clients("glance").images.create.return_value = self.image
|
||||||
|
return_image = utils.GlanceScenario()._create_image('image_name',
|
||||||
|
'image_url',
|
||||||
|
'container_format',
|
||||||
|
'disk_format')
|
||||||
|
self.wait_for.mock.assert_called_once_with(self.image,
|
||||||
|
update_resource=self.gfm(),
|
||||||
|
is_ready=self.res_is.mock(),
|
||||||
|
check_interval=3,
|
||||||
|
timeout=120)
|
||||||
|
self.res_is.mock.assert_has_calls(mock.call('active'))
|
||||||
|
self.assertEqual(self.wait_for.mock(), return_image)
|
||||||
|
|
||||||
|
def test__delete_image(self):
|
||||||
|
utils.GlanceScenario()._delete_image(self.image)
|
||||||
|
self.image.delete.assert_called_once_with()
|
||||||
|
self.wait_for_delete.\
|
||||||
|
mock.assert_called_once_with(self.image,
|
||||||
|
update_resource=self.gfm(),
|
||||||
|
check_interval=3,
|
||||||
|
timeout=120)
|
@ -319,7 +319,7 @@ class NovaServersTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_snapshot_server(self):
|
def test_snapshot_server(self):
|
||||||
fake_server = object()
|
fake_server = object()
|
||||||
fake_image = fakes.FakeImageManager().create()
|
fake_image = fakes.FakeImageManager()._create()
|
||||||
fake_image.id = "image_id"
|
fake_image.id = "image_id"
|
||||||
|
|
||||||
scenario = servers.NovaServers()
|
scenario = servers.NovaServers()
|
||||||
|
@ -378,9 +378,10 @@ class ResourceCleanerTestCase(test.TestCase):
|
|||||||
client = clients[index]
|
client = clients[index]
|
||||||
nova = client["nova"]
|
nova = client["nova"]
|
||||||
cinder = client["cinder"]
|
cinder = client["cinder"]
|
||||||
|
glance = client["glance"]
|
||||||
for count in range(3):
|
for count in range(3):
|
||||||
uid = index + count
|
uid = index + count
|
||||||
img = nova.images.create()
|
img = glance.images._create()
|
||||||
nova.servers.create("svr-%s" % (uid), img.uuid, index)
|
nova.servers.create("svr-%s" % (uid), img.uuid, index)
|
||||||
nova.keypairs.create("keypair-%s" % (uid))
|
nova.keypairs.create("keypair-%s" % (uid))
|
||||||
nova.security_groups.create("secgroup-%s" % (uid))
|
nova.security_groups.create("secgroup-%s" % (uid))
|
||||||
@ -402,6 +403,7 @@ class ResourceCleanerTestCase(test.TestCase):
|
|||||||
for client in clients:
|
for client in clients:
|
||||||
nova = client["nova"]
|
nova = client["nova"]
|
||||||
cinder = client["cinder"]
|
cinder = client["cinder"]
|
||||||
|
glance = client["glance"]
|
||||||
_assert_purged(nova.servers, "servers")
|
_assert_purged(nova.servers, "servers")
|
||||||
_assert_purged(nova.keypairs, "key pairs")
|
_assert_purged(nova.keypairs, "key pairs")
|
||||||
_assert_purged(nova.security_groups, "security groups")
|
_assert_purged(nova.security_groups, "security groups")
|
||||||
@ -413,7 +415,7 @@ class ResourceCleanerTestCase(test.TestCase):
|
|||||||
_assert_purged(cinder.transfers, "volume transfers")
|
_assert_purged(cinder.transfers, "volume transfers")
|
||||||
_assert_purged(cinder.volume_snapshots, "volume snapshots")
|
_assert_purged(cinder.volume_snapshots, "volume snapshots")
|
||||||
|
|
||||||
for image in nova.images.list():
|
for image in glance.images.list():
|
||||||
self.assertEqual("DELETED", image.status,
|
self.assertEqual("DELETED", image.status,
|
||||||
"image not purged: %s" % (image))
|
"image not purged: %s" % (image))
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from glanceclient import exc
|
||||||
from novaclient import exceptions
|
from novaclient import exceptions
|
||||||
from rally.benchmark import base
|
from rally.benchmark import base
|
||||||
from rally import utils as rally_utils
|
from rally import utils as rally_utils
|
||||||
@ -56,6 +57,14 @@ class FakeImage(FakeResource):
|
|||||||
|
|
||||||
def __init__(self, manager=None):
|
def __init__(self, manager=None):
|
||||||
super(FakeImage, self).__init__(manager)
|
super(FakeImage, self).__init__(manager)
|
||||||
|
self.id = "image-id-0"
|
||||||
|
|
||||||
|
|
||||||
|
class FakeFailedImage(FakeResource):
|
||||||
|
|
||||||
|
def __init__(self, manager=None):
|
||||||
|
super(FakeFailedImage, self).__init__(manager)
|
||||||
|
self.status = "error"
|
||||||
|
|
||||||
|
|
||||||
class FakeFloatingIP(FakeResource):
|
class FakeFloatingIP(FakeResource):
|
||||||
@ -174,7 +183,7 @@ class FakeServerManager(FakeManager):
|
|||||||
return self._create(name=name)
|
return self._create(name=name)
|
||||||
|
|
||||||
def create_image(self, server, name):
|
def create_image(self, server, name):
|
||||||
image = self.images.create()
|
image = self.images._create()
|
||||||
return image.uuid
|
return image.uuid
|
||||||
|
|
||||||
def add_floating_ip(self, server, fip):
|
def add_floating_ip(self, server, fip):
|
||||||
@ -192,8 +201,29 @@ class FakeFailedServerManager(FakeServerManager):
|
|||||||
|
|
||||||
class FakeImageManager(FakeManager):
|
class FakeImageManager(FakeManager):
|
||||||
|
|
||||||
def create(self):
|
def __init__(self):
|
||||||
return self._cache(FakeImage(self))
|
super(FakeImageManager, self).__init__()
|
||||||
|
|
||||||
|
def get(self, resource_uuid):
|
||||||
|
image = self.cache.get(resource_uuid, None)
|
||||||
|
if image is not None:
|
||||||
|
return image
|
||||||
|
raise exc.HTTPNotFound("Image %s not found" % (resource_uuid))
|
||||||
|
|
||||||
|
def _create(self, image_class=FakeImage, name=None):
|
||||||
|
image = self._cache(image_class(self))
|
||||||
|
if name is not None:
|
||||||
|
image.name = name
|
||||||
|
return image
|
||||||
|
|
||||||
|
def create(self, name, copy_from, container_format, disk_format):
|
||||||
|
return self._create(name=name)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeFailedImageManager(FakeImageManager):
|
||||||
|
|
||||||
|
def create(self, name, copy_from, container_format, disk_format):
|
||||||
|
return self._create(FakeFailedImage, name)
|
||||||
|
|
||||||
|
|
||||||
class FakeFloatingIPsManager(FakeManager):
|
class FakeFloatingIPsManager(FakeManager):
|
||||||
@ -309,9 +339,11 @@ class FakeServiceCatalog(object):
|
|||||||
|
|
||||||
class FakeGlanceClient(object):
|
class FakeGlanceClient(object):
|
||||||
|
|
||||||
def __init__(self, nova_client=None):
|
def __init__(self, failed_image_manager=False):
|
||||||
if nova_client:
|
if failed_image_manager:
|
||||||
self.images = nova_client.images
|
self.images = FakeFailedImageManager()
|
||||||
|
else:
|
||||||
|
self.images = FakeImageManager()
|
||||||
|
|
||||||
|
|
||||||
class FakeCinderClient(object):
|
class FakeCinderClient(object):
|
||||||
@ -383,7 +415,7 @@ class FakeClients(object):
|
|||||||
def get_glance_client(self):
|
def get_glance_client(self):
|
||||||
if self.glance is not None:
|
if self.glance is not None:
|
||||||
return self.glance
|
return self.glance
|
||||||
self.glance = FakeGlanceClient(self.get_nova_client())
|
self.glance = FakeGlanceClient()
|
||||||
return self.glance
|
return self.glance
|
||||||
|
|
||||||
def get_cinder_client(self):
|
def get_cinder_client(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user