Add wrapper for Glance API versions
Adds a new Glance wrapper that abstracts creation and deletion of images for the two versions of the Glance API. A functional test is added as well. Change-Id: I3a6857f07415da1c8fc8761b3dd012aab0c67460
This commit is contained in:
parent
d324f6b76b
commit
067af09a41
@ -825,6 +825,26 @@
|
|||||||
failure_rate:
|
failure_rate:
|
||||||
max: 0
|
max: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
args:
|
||||||
|
image_location: "{{ cirros_image_url }}"
|
||||||
|
container_format: "bare"
|
||||||
|
disk_format: "qcow2"
|
||||||
|
runner:
|
||||||
|
type: "constant"
|
||||||
|
times: 1
|
||||||
|
concurrency: 1
|
||||||
|
context:
|
||||||
|
users:
|
||||||
|
tenants: 2
|
||||||
|
users_per_tenant: 3
|
||||||
|
api_versions:
|
||||||
|
glance:
|
||||||
|
version: 2
|
||||||
|
sla:
|
||||||
|
failure_rate:
|
||||||
|
max: 0
|
||||||
|
|
||||||
GlanceImages.create_and_list_image:
|
GlanceImages.create_and_list_image:
|
||||||
-
|
-
|
||||||
args:
|
args:
|
||||||
|
@ -13,41 +13,9 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from rally.plugins.openstack import scenario
|
from rally.plugins.openstack import scenario
|
||||||
|
from rally.plugins.openstack.wrappers import glance as glance_wrapper
|
||||||
from rally.task import atomic
|
from rally.task import atomic
|
||||||
from rally.task import utils
|
|
||||||
|
|
||||||
|
|
||||||
GLANCE_BENCHMARK_OPTS = [
|
|
||||||
cfg.FloatOpt("glance_image_create_prepoll_delay",
|
|
||||||
default=2.0,
|
|
||||||
help="Time to sleep after creating a resource before "
|
|
||||||
"polling for it status"),
|
|
||||||
cfg.FloatOpt("glance_image_create_timeout",
|
|
||||||
default=120.0,
|
|
||||||
help="Time to wait for glance image to be created."),
|
|
||||||
cfg.FloatOpt("glance_image_create_poll_interval",
|
|
||||||
default=1.0,
|
|
||||||
help="Interval between checks when waiting for image "
|
|
||||||
"creation."),
|
|
||||||
cfg.FloatOpt("glance_image_delete_timeout",
|
|
||||||
default=120.0,
|
|
||||||
help="Time to wait for glance image to be deleted."),
|
|
||||||
cfg.FloatOpt("glance_image_delete_poll_interval",
|
|
||||||
default=1.0,
|
|
||||||
help="Interval between checks when waiting for image "
|
|
||||||
"deletion.")
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
benchmark_group = cfg.OptGroup(name="benchmark", title="benchmark options")
|
|
||||||
CONF.register_opts(GLANCE_BENCHMARK_OPTS, group=benchmark_group)
|
|
||||||
|
|
||||||
|
|
||||||
class GlanceScenario(scenario.OpenStackScenario):
|
class GlanceScenario(scenario.OpenStackScenario):
|
||||||
@ -72,38 +40,9 @@ class GlanceScenario(scenario.OpenStackScenario):
|
|||||||
|
|
||||||
:returns: image object
|
:returns: image object
|
||||||
"""
|
"""
|
||||||
kw = {
|
client = glance_wrapper.wrap(self._clients.glance, self)
|
||||||
"name": self.generate_random_name(),
|
return client.create_image(container_format, image_location,
|
||||||
"container_format": container_format,
|
disk_format)
|
||||||
"disk_format": disk_format,
|
|
||||||
}
|
|
||||||
|
|
||||||
kw.update(kwargs)
|
|
||||||
image_location = os.path.expanduser(image_location)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if os.path.isfile(image_location):
|
|
||||||
kw["data"] = open(image_location)
|
|
||||||
else:
|
|
||||||
kw["copy_from"] = image_location
|
|
||||||
|
|
||||||
image = self.clients("glance").images.create(**kw)
|
|
||||||
|
|
||||||
time.sleep(CONF.benchmark.glance_image_create_prepoll_delay)
|
|
||||||
|
|
||||||
image = utils.wait_for(
|
|
||||||
image,
|
|
||||||
ready_statuses=["active"],
|
|
||||||
update_resource=utils.get_from_manager(),
|
|
||||||
timeout=CONF.benchmark.glance_image_create_timeout,
|
|
||||||
check_interval=CONF.benchmark.
|
|
||||||
glance_image_create_poll_interval)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
if "data" in kw:
|
|
||||||
kw["data"].close()
|
|
||||||
|
|
||||||
return image
|
|
||||||
|
|
||||||
@atomic.action_timer("glance.delete_image")
|
@atomic.action_timer("glance.delete_image")
|
||||||
def _delete_image(self, image):
|
def _delete_image(self, image):
|
||||||
@ -113,11 +52,5 @@ class GlanceScenario(scenario.OpenStackScenario):
|
|||||||
|
|
||||||
:param image: Image object
|
:param image: Image object
|
||||||
"""
|
"""
|
||||||
image.delete()
|
client = glance_wrapper.wrap(self._clients.glance, self)
|
||||||
utils.wait_for_status(
|
client.delete_image(image)
|
||||||
image,
|
|
||||||
ready_statuses=["deleted"],
|
|
||||||
check_deletion=True,
|
|
||||||
update_resource=utils.get_from_manager(),
|
|
||||||
timeout=CONF.benchmark.glance_image_delete_timeout,
|
|
||||||
check_interval=CONF.benchmark.glance_image_delete_poll_interval)
|
|
||||||
|
187
rally/plugins/openstack/wrappers/glance.py
Normal file
187
rally/plugins/openstack/wrappers/glance.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# Copyright 2016: 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 abc
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from rally.common import logging
|
||||||
|
from rally import exceptions
|
||||||
|
from rally.task import utils
|
||||||
|
|
||||||
|
from glanceclient import exc as glance_exc
|
||||||
|
from oslo_config import cfg
|
||||||
|
import requests
|
||||||
|
import six
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
GLANCE_BENCHMARK_OPTS = [
|
||||||
|
cfg.FloatOpt("glance_image_create_prepoll_delay",
|
||||||
|
default=2.0,
|
||||||
|
help="Time to sleep after creating a resource before "
|
||||||
|
"polling for it status"),
|
||||||
|
cfg.FloatOpt("glance_image_create_timeout",
|
||||||
|
default=120.0,
|
||||||
|
help="Time to wait for glance image to be created."),
|
||||||
|
cfg.FloatOpt("glance_image_create_poll_interval",
|
||||||
|
default=1.0,
|
||||||
|
help="Interval between checks when waiting for image "
|
||||||
|
"creation."),
|
||||||
|
cfg.FloatOpt("glance_image_delete_timeout",
|
||||||
|
default=120.0,
|
||||||
|
help="Time to wait for glance image to be deleted."),
|
||||||
|
cfg.FloatOpt("glance_image_delete_poll_interval",
|
||||||
|
default=1.0,
|
||||||
|
help="Interval between checks when waiting for image "
|
||||||
|
"deletion.")
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
benchmark_group = cfg.OptGroup(name="benchmark", title="benchmark options")
|
||||||
|
CONF.register_opts(GLANCE_BENCHMARK_OPTS, group=benchmark_group)
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class GlanceWrapper(object):
|
||||||
|
def __init__(self, client, owner):
|
||||||
|
self.owner = owner
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create_image(self, container_format, image_location, disk_format):
|
||||||
|
"""Creates new image."""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def delete_image(self, image):
|
||||||
|
"""Deletes image."""
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceV1Wrapper(GlanceWrapper):
|
||||||
|
def create_image(self, container_format, image_location,
|
||||||
|
disk_format, **kwargs):
|
||||||
|
kw = {
|
||||||
|
"container_format": container_format,
|
||||||
|
"disk_format": disk_format,
|
||||||
|
}
|
||||||
|
kw.update(kwargs)
|
||||||
|
if "name" not in kw:
|
||||||
|
kw["name"] = self.owner.generate_random_name()
|
||||||
|
image_location = os.path.expanduser(image_location)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.isfile(image_location):
|
||||||
|
kw["data"] = open(image_location)
|
||||||
|
else:
|
||||||
|
kw["copy_from"] = image_location
|
||||||
|
|
||||||
|
image = self.client.images.create(**kw)
|
||||||
|
|
||||||
|
time.sleep(CONF.benchmark.glance_image_create_prepoll_delay)
|
||||||
|
|
||||||
|
image = utils.wait_for_status(
|
||||||
|
image, ["active"],
|
||||||
|
update_resource=utils.get_from_manager(),
|
||||||
|
timeout=CONF.benchmark.glance_image_create_timeout,
|
||||||
|
check_interval=CONF.benchmark.
|
||||||
|
glance_image_create_poll_interval)
|
||||||
|
finally:
|
||||||
|
if "data" in kw:
|
||||||
|
kw["data"].close()
|
||||||
|
|
||||||
|
return image
|
||||||
|
|
||||||
|
def delete_image(self, image):
|
||||||
|
image.delete()
|
||||||
|
utils.wait_for_status(
|
||||||
|
image, ["deleted"],
|
||||||
|
check_deletion=True,
|
||||||
|
update_resource=utils.get_from_manager(),
|
||||||
|
timeout=CONF.benchmark.glance_image_delete_timeout,
|
||||||
|
check_interval=CONF.benchmark.glance_image_delete_poll_interval)
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceV2Wrapper(GlanceWrapper):
|
||||||
|
def _get_image(self, image):
|
||||||
|
try:
|
||||||
|
return self.client.images.get(image.id)
|
||||||
|
except glance_exc.HTTPNotFound:
|
||||||
|
raise exceptions.GetResourceNotFound(resource=image)
|
||||||
|
|
||||||
|
def create_image(self, container_format, image_location,
|
||||||
|
disk_format, **kwargs):
|
||||||
|
kw = {
|
||||||
|
"container_format": container_format,
|
||||||
|
"disk_format": disk_format,
|
||||||
|
}
|
||||||
|
kw.update(kwargs)
|
||||||
|
if "name" not in kw:
|
||||||
|
kw["name"] = self.owner.generate_random_name()
|
||||||
|
|
||||||
|
image_location = os.path.expanduser(image_location)
|
||||||
|
|
||||||
|
image = self.client.images.create(**kw)
|
||||||
|
|
||||||
|
time.sleep(CONF.benchmark.glance_image_create_prepoll_delay)
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
image = utils.wait_for_status(
|
||||||
|
image, ["queued"],
|
||||||
|
update_resource=self._get_image,
|
||||||
|
timeout=CONF.benchmark.glance_image_create_timeout,
|
||||||
|
check_interval=CONF.benchmark.
|
||||||
|
glance_image_create_poll_interval)
|
||||||
|
timeout = time.time() - start
|
||||||
|
|
||||||
|
image_data = None
|
||||||
|
try:
|
||||||
|
if os.path.isfile(image_location):
|
||||||
|
image_data = open(image_location)
|
||||||
|
else:
|
||||||
|
response = requests.get(image_location)
|
||||||
|
image_data = response.raw
|
||||||
|
self.client.images.upload(image.id, image_data)
|
||||||
|
finally:
|
||||||
|
if image_data is not None:
|
||||||
|
image_data.close()
|
||||||
|
|
||||||
|
return utils.wait_for_status(
|
||||||
|
image, ["active"],
|
||||||
|
update_resource=self._get_image,
|
||||||
|
timeout=timeout,
|
||||||
|
check_interval=CONF.benchmark.
|
||||||
|
glance_image_create_poll_interval)
|
||||||
|
|
||||||
|
def delete_image(self, image):
|
||||||
|
self.client.images.delete(image.id)
|
||||||
|
utils.wait_for_status(
|
||||||
|
image, ["deleted"],
|
||||||
|
check_deletion=True,
|
||||||
|
update_resource=self._get_image,
|
||||||
|
timeout=CONF.benchmark.glance_image_delete_timeout,
|
||||||
|
check_interval=CONF.benchmark.glance_image_delete_poll_interval)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap(client, owner):
|
||||||
|
"""Returns glanceclient wrapper based on glance client version."""
|
||||||
|
version = client.choose_version()
|
||||||
|
if version == "1":
|
||||||
|
return GlanceV1Wrapper(client(), owner)
|
||||||
|
elif version == "2":
|
||||||
|
return GlanceV2Wrapper(client(), owner)
|
||||||
|
else:
|
||||||
|
msg = "Version %s of the glance API could not be identified." % version
|
||||||
|
LOG.warning(msg)
|
||||||
|
raise exceptions.InvalidArgumentsException(msg)
|
@ -30,6 +30,7 @@ from rally.common import objects
|
|||||||
from rally.common import utils
|
from rally.common import utils
|
||||||
from rally import exceptions
|
from rally import exceptions
|
||||||
from rally import osclients
|
from rally import osclients
|
||||||
|
from rally.plugins.openstack.wrappers import glance as glance_wrapper
|
||||||
from rally.plugins.openstack.wrappers import network
|
from rally.plugins.openstack.wrappers import network
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -406,13 +407,13 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
|
|||||||
.format(opt=option, opt_val=option_value))
|
.format(opt=option, opt_val=option_value))
|
||||||
|
|
||||||
def _discover_or_create_image(self):
|
def _discover_or_create_image(self):
|
||||||
glanceclient = self.clients.glance()
|
glance_wrap = glance_wrapper.wrap(self.clients.glance, self)
|
||||||
|
|
||||||
if CONF.image.name_regex:
|
if CONF.image.name_regex:
|
||||||
LOG.debug("Trying to discover an image with name matching "
|
LOG.debug("Trying to discover an image with name matching "
|
||||||
"regular expression '%s'. Note that case insensitive "
|
"regular expression '%s'. Note that case insensitive "
|
||||||
"matching is performed" % CONF.image.name_regex)
|
"matching is performed" % CONF.image.name_regex)
|
||||||
img_list = [img for img in glanceclient.images.list()
|
img_list = [img for img in self.clients.glance().images.list()
|
||||||
if img.status.lower() == "active" and img.name]
|
if img.status.lower() == "active" and img.name]
|
||||||
for img in img_list:
|
for img in img_list:
|
||||||
if re.match(CONF.image.name_regex, img.name, re.IGNORECASE):
|
if re.match(CONF.image.name_regex, img.name, re.IGNORECASE):
|
||||||
@ -427,13 +428,13 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
|
|||||||
"name": self.generate_random_name(),
|
"name": self.generate_random_name(),
|
||||||
"disk_format": CONF.image.disk_format,
|
"disk_format": CONF.image.disk_format,
|
||||||
"container_format": CONF.image.container_format,
|
"container_format": CONF.image.container_format,
|
||||||
|
"image_location": os.path.join(_create_or_get_data_dir(),
|
||||||
|
self.image_name),
|
||||||
"is_public": True
|
"is_public": True
|
||||||
}
|
}
|
||||||
LOG.debug("Creating image '%s'" % params["name"])
|
LOG.debug("Creating image '%s'" % params["name"])
|
||||||
image = glanceclient.images.create(**params)
|
image = glance_wrap.create_image(**params)
|
||||||
self._created_images.append(image)
|
self._created_images.append(image)
|
||||||
image.update(data=open(
|
|
||||||
os.path.join(_create_or_get_data_dir(), self.image_name), "rb"))
|
|
||||||
|
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
@ -15,13 +15,11 @@
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from rally.plugins.openstack.scenarios.glance import utils
|
from rally.plugins.openstack.scenarios.glance import utils
|
||||||
from tests.unit import test
|
from tests.unit import test
|
||||||
|
|
||||||
GLANCE_UTILS = "rally.plugins.openstack.scenarios.glance.utils"
|
GLANCE_UTILS = "rally.plugins.openstack.scenarios.glance.utils"
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class GlanceScenarioTestCase(test.ScenarioTestCase):
|
class GlanceScenarioTestCase(test.ScenarioTestCase):
|
||||||
@ -30,6 +28,8 @@ class GlanceScenarioTestCase(test.ScenarioTestCase):
|
|||||||
super(GlanceScenarioTestCase, self).setUp()
|
super(GlanceScenarioTestCase, self).setUp()
|
||||||
self.image = mock.Mock()
|
self.image = mock.Mock()
|
||||||
self.image1 = mock.Mock()
|
self.image1 = mock.Mock()
|
||||||
|
self.scenario_clients = mock.Mock()
|
||||||
|
self.scenario_clients.glance.choose_version.return_value = 1
|
||||||
|
|
||||||
def test_list_images(self):
|
def test_list_images(self):
|
||||||
scenario = utils.GlanceScenario(context=self.context)
|
scenario = utils.GlanceScenario(context=self.context)
|
||||||
@ -40,52 +40,28 @@ class GlanceScenarioTestCase(test.ScenarioTestCase):
|
|||||||
self._test_atomic_action_timer(scenario.atomic_actions(),
|
self._test_atomic_action_timer(scenario.atomic_actions(),
|
||||||
"glance.list_images")
|
"glance.list_images")
|
||||||
|
|
||||||
def test_create_image(self):
|
@mock.patch("rally.plugins.openstack.wrappers.glance.wrap")
|
||||||
|
def test_create_image(self, mock_wrap):
|
||||||
image_location = tempfile.NamedTemporaryFile()
|
image_location = tempfile.NamedTemporaryFile()
|
||||||
self.clients("glance").images.create.return_value = self.image
|
mock_wrap.return_value.create_image.return_value = self.image
|
||||||
scenario = utils.GlanceScenario(context=self.context)
|
scenario = utils.GlanceScenario(context=self.context,
|
||||||
|
clients=self.scenario_clients)
|
||||||
return_image = scenario._create_image("container_format",
|
return_image = scenario._create_image("container_format",
|
||||||
image_location.name,
|
image_location.name,
|
||||||
"disk_format")
|
"disk_format")
|
||||||
self.mock_wait_for.mock.assert_called_once_with(
|
self.assertEqual(self.image, return_image)
|
||||||
self.image,
|
mock_wrap.assert_called_once_with(scenario._clients.glance, scenario)
|
||||||
update_resource=self.mock_get_from_manager.mock.return_value,
|
mock_wrap.return_value.create_image.assert_called_once_with(
|
||||||
ready_statuses=["active"],
|
"container_format", image_location.name, "disk_format")
|
||||||
check_interval=CONF.benchmark.glance_image_create_poll_interval,
|
|
||||||
timeout=CONF.benchmark.glance_image_create_timeout)
|
|
||||||
self.mock_get_from_manager.mock.assert_called_once_with()
|
|
||||||
self.assertEqual(self.mock_wait_for.mock.return_value, return_image)
|
|
||||||
self._test_atomic_action_timer(scenario.atomic_actions(),
|
self._test_atomic_action_timer(scenario.atomic_actions(),
|
||||||
"glance.create_image")
|
"glance.create_image")
|
||||||
|
|
||||||
def test_create_image_with_location(self):
|
@mock.patch("rally.plugins.openstack.wrappers.glance.wrap")
|
||||||
self.clients("glance").images.create.return_value = self.image
|
def test_delete_image(self, mock_wrap):
|
||||||
scenario = utils.GlanceScenario(context=self.context)
|
scenario = utils.GlanceScenario(context=self.context,
|
||||||
return_image = scenario._create_image("container_format",
|
clients=self.scenario_clients)
|
||||||
"image_location",
|
|
||||||
"disk_format")
|
|
||||||
self.mock_wait_for.mock.assert_called_once_with(
|
|
||||||
self.image,
|
|
||||||
update_resource=self.mock_get_from_manager.mock.return_value,
|
|
||||||
ready_statuses=["active"],
|
|
||||||
check_interval=CONF.benchmark.glance_image_create_poll_interval,
|
|
||||||
timeout=CONF.benchmark.glance_image_create_timeout)
|
|
||||||
self.mock_get_from_manager.mock.assert_called_once_with()
|
|
||||||
self.assertEqual(self.mock_wait_for.mock.return_value, return_image)
|
|
||||||
self._test_atomic_action_timer(scenario.atomic_actions(),
|
|
||||||
"glance.create_image")
|
|
||||||
|
|
||||||
def test_delete_image(self):
|
|
||||||
scenario = utils.GlanceScenario(context=self.context)
|
|
||||||
scenario._delete_image(self.image)
|
scenario._delete_image(self.image)
|
||||||
self.image.delete.assert_called_once_with()
|
mock_wrap.assert_called_once_with(scenario._clients.glance, scenario)
|
||||||
self.mock_wait_for_status.mock.assert_called_once_with(
|
mock_wrap.return_value.delete_image.assert_called_once_with(self.image)
|
||||||
self.image,
|
|
||||||
ready_statuses=["deleted"],
|
|
||||||
check_deletion=True,
|
|
||||||
update_resource=self.mock_get_from_manager.mock.return_value,
|
|
||||||
check_interval=CONF.benchmark.glance_image_delete_poll_interval,
|
|
||||||
timeout=CONF.benchmark.glance_image_delete_timeout)
|
|
||||||
self.mock_get_from_manager.mock.assert_called_once_with()
|
|
||||||
self._test_atomic_action_timer(scenario.atomic_actions(),
|
self._test_atomic_action_timer(scenario.atomic_actions(),
|
||||||
"glance.delete_image")
|
"glance.delete_image")
|
||||||
|
190
tests/unit/plugins/openstack/wrappers/test_glance.py
Normal file
190
tests/unit/plugins/openstack/wrappers/test_glance.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# 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 tempfile
|
||||||
|
|
||||||
|
import ddt
|
||||||
|
from glanceclient import exc as glance_exc
|
||||||
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from rally import exceptions
|
||||||
|
from rally.plugins.openstack.wrappers import glance as glance_wrapper
|
||||||
|
from tests.unit import test
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceWrapperTestBase(object):
|
||||||
|
def test_wrap(self):
|
||||||
|
client = mock.MagicMock()
|
||||||
|
owner = mock.Mock()
|
||||||
|
client.version = "dummy"
|
||||||
|
self.assertRaises(exceptions.InvalidArgumentsException,
|
||||||
|
glance_wrapper.wrap, client, owner)
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
|
class GlanceV1WrapperTestCase(test.ScenarioTestCase, GlanceWrapperTestBase):
|
||||||
|
_tempfile = tempfile.NamedTemporaryFile()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(GlanceV1WrapperTestCase, self).setUp()
|
||||||
|
self.client = mock.MagicMock()
|
||||||
|
self.client.choose_version.return_value = "1"
|
||||||
|
self.owner = mock.Mock()
|
||||||
|
self.wrapped_client = glance_wrapper.wrap(self.client, self.owner)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
{"location": "image_location"},
|
||||||
|
{"location": "image_location", "fakearg": "fake"},
|
||||||
|
{"location": "image_location", "name": "image_name"},
|
||||||
|
{"location": _tempfile.name})
|
||||||
|
@ddt.unpack
|
||||||
|
@mock.patch("six.moves.builtins.open")
|
||||||
|
def test_create_image(self, mock_open, location, **kwargs):
|
||||||
|
return_image = self.wrapped_client.create_image("container_format",
|
||||||
|
location,
|
||||||
|
"disk_format",
|
||||||
|
**kwargs)
|
||||||
|
call_args = dict(kwargs)
|
||||||
|
call_args["container_format"] = "container_format"
|
||||||
|
call_args["disk_format"] = "disk_format"
|
||||||
|
if location.startswith("/"):
|
||||||
|
call_args["data"] = mock_open.return_value
|
||||||
|
mock_open.assert_called_once_with(location)
|
||||||
|
mock_open.return_value.close.assert_called_once_with()
|
||||||
|
else:
|
||||||
|
call_args["copy_from"] = location
|
||||||
|
if "name" not in kwargs:
|
||||||
|
call_args["name"] = self.owner.generate_random_name.return_value
|
||||||
|
|
||||||
|
self.client().images.create.assert_called_once_with(**call_args)
|
||||||
|
|
||||||
|
self.mock_wait_for_status.mock.assert_called_once_with(
|
||||||
|
self.client().images.create.return_value, ["active"],
|
||||||
|
update_resource=self.mock_get_from_manager.mock.return_value,
|
||||||
|
check_interval=CONF.benchmark.glance_image_create_poll_interval,
|
||||||
|
timeout=CONF.benchmark.glance_image_create_timeout)
|
||||||
|
self.mock_get_from_manager.mock.assert_called_once_with()
|
||||||
|
self.assertEqual(self.mock_wait_for_status.mock.return_value,
|
||||||
|
return_image)
|
||||||
|
|
||||||
|
def test_delete_image(self):
|
||||||
|
image = mock.Mock()
|
||||||
|
self.wrapped_client.delete_image(image)
|
||||||
|
image.delete.assert_called_once_with()
|
||||||
|
self.mock_wait_for_status.mock.assert_called_once_with(
|
||||||
|
image, ["deleted"],
|
||||||
|
check_deletion=True,
|
||||||
|
update_resource=self.mock_get_from_manager.mock.return_value,
|
||||||
|
check_interval=CONF.benchmark.glance_image_delete_poll_interval,
|
||||||
|
timeout=CONF.benchmark.glance_image_delete_timeout)
|
||||||
|
self.mock_get_from_manager.mock.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
|
class GlanceV2WrapperTestCase(test.ScenarioTestCase, GlanceWrapperTestBase):
|
||||||
|
_tempfile = tempfile.NamedTemporaryFile()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(GlanceV2WrapperTestCase, self).setUp()
|
||||||
|
self.client = mock.MagicMock()
|
||||||
|
self.client.choose_version.return_value = "2"
|
||||||
|
self.owner = mock.Mock()
|
||||||
|
self.wrapped_client = glance_wrapper.wrap(self.client, self.owner)
|
||||||
|
|
||||||
|
def test__get_image(self):
|
||||||
|
image = mock.Mock()
|
||||||
|
|
||||||
|
return_image = self.wrapped_client._get_image(image)
|
||||||
|
|
||||||
|
self.client.return_value.images.get.assert_called_once_with(image.id)
|
||||||
|
self.assertEqual(return_image,
|
||||||
|
self.client.return_value.images.get.return_value)
|
||||||
|
|
||||||
|
def test__get_image_not_found(self):
|
||||||
|
image = mock.Mock()
|
||||||
|
self.client.return_value.images.get.side_effect = (
|
||||||
|
glance_exc.HTTPNotFound)
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.GetResourceNotFound,
|
||||||
|
self.wrapped_client._get_image, image)
|
||||||
|
self.client.return_value.images.get.assert_called_once_with(image.id)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
{"location": "image_location"},
|
||||||
|
{"location": "image_location", "fakearg": "fake"},
|
||||||
|
{"location": "image_location", "name": "image_name"},
|
||||||
|
{"location": _tempfile.name})
|
||||||
|
@ddt.unpack
|
||||||
|
@mock.patch("six.moves.builtins.open")
|
||||||
|
@mock.patch("requests.get")
|
||||||
|
def test_create_image(self, mock_requests_get, mock_open, location,
|
||||||
|
**kwargs):
|
||||||
|
self.wrapped_client._get_image = mock.Mock()
|
||||||
|
created_image = mock.Mock()
|
||||||
|
uploaded_image = mock.Mock()
|
||||||
|
self.mock_wait_for_status.mock.side_effect = [created_image,
|
||||||
|
uploaded_image]
|
||||||
|
|
||||||
|
return_image = self.wrapped_client.create_image("container_format",
|
||||||
|
location,
|
||||||
|
"disk_format",
|
||||||
|
**kwargs)
|
||||||
|
create_args = dict(kwargs)
|
||||||
|
create_args["container_format"] = "container_format"
|
||||||
|
create_args["disk_format"] = "disk_format"
|
||||||
|
if "name" not in kwargs:
|
||||||
|
create_args["name"] = self.owner.generate_random_name.return_value
|
||||||
|
|
||||||
|
self.client().images.create.assert_called_once_with(**create_args)
|
||||||
|
|
||||||
|
if location.startswith("/"):
|
||||||
|
data = mock_open.return_value
|
||||||
|
mock_open.assert_called_once_with(location)
|
||||||
|
else:
|
||||||
|
data = mock_requests_get.return_value.raw
|
||||||
|
mock_requests_get.assert_called_once_with(location)
|
||||||
|
data.close.assert_called_once_with()
|
||||||
|
self.client().images.upload.assert_called_once_with(created_image.id,
|
||||||
|
data)
|
||||||
|
|
||||||
|
self.mock_wait_for_status.mock.assert_has_calls([
|
||||||
|
mock.call(
|
||||||
|
self.client().images.create.return_value, ["queued"],
|
||||||
|
update_resource=self.wrapped_client._get_image,
|
||||||
|
check_interval=CONF.benchmark.
|
||||||
|
glance_image_create_poll_interval,
|
||||||
|
timeout=CONF.benchmark.glance_image_create_timeout),
|
||||||
|
mock.call(
|
||||||
|
created_image, ["active"],
|
||||||
|
update_resource=self.wrapped_client._get_image,
|
||||||
|
check_interval=CONF.benchmark.
|
||||||
|
glance_image_create_poll_interval,
|
||||||
|
timeout=mock.ANY)])
|
||||||
|
self.assertEqual(uploaded_image, return_image)
|
||||||
|
|
||||||
|
def test_delete_image(self):
|
||||||
|
image = mock.Mock()
|
||||||
|
self.wrapped_client.delete_image(image)
|
||||||
|
self.client.return_value.images.delete.assert_called_once_with(
|
||||||
|
image.id)
|
||||||
|
self.mock_wait_for_status.mock.assert_called_once_with(
|
||||||
|
image, ["deleted"],
|
||||||
|
check_deletion=True,
|
||||||
|
update_resource=self.wrapped_client._get_image,
|
||||||
|
check_interval=CONF.benchmark.glance_image_delete_poll_interval,
|
||||||
|
timeout=CONF.benchmark.glance_image_delete_timeout)
|
@ -396,7 +396,8 @@ class TempestResourcesContextTestCase(test.TestCase):
|
|||||||
result = self.context.conf.get("compute", "flavor_ref")
|
result = self.context.conf.get("compute", "flavor_ref")
|
||||||
self.assertEqual("id1", result)
|
self.assertEqual("id1", result)
|
||||||
|
|
||||||
def test__discover_or_create_image_when_image_exists(self):
|
@mock.patch("rally.plugins.openstack.wrappers.glance.wrap")
|
||||||
|
def test__discover_or_create_image_when_image_exists(self, mock_wrap):
|
||||||
client = self.context.clients.glance()
|
client = self.context.clients.glance()
|
||||||
client.images.list.return_value = [fakes.FakeResource(name="CirrOS",
|
client.images.list.return_value = [fakes.FakeResource(name="CirrOS",
|
||||||
status="active")]
|
status="active")]
|
||||||
@ -404,14 +405,31 @@ class TempestResourcesContextTestCase(test.TestCase):
|
|||||||
self.assertEqual("CirrOS", image.name)
|
self.assertEqual("CirrOS", image.name)
|
||||||
self.assertEqual(0, len(self.context._created_images))
|
self.assertEqual(0, len(self.context._created_images))
|
||||||
|
|
||||||
@mock.patch("six.moves.builtins.open")
|
# @mock.patch("six.moves.builtins.open")
|
||||||
def test__discover_or_create_image(self, mock_open):
|
# def test__discover_or_create_image(self, mock_wrap, mock_open):
|
||||||
client = self.context.clients.glance()
|
# client = self.context.clients.glance()
|
||||||
client.images.create.side_effect = [fakes.FakeImage(id="id1")]
|
# client.images.create.side_effect = [fakes.FakeImage(id="id1")]
|
||||||
|
|
||||||
|
# image = self.context._discover_or_create_image()
|
||||||
|
# self.assertEqual("id1", image.id)
|
||||||
|
# self.assertEqual("id1", self.context._created_images[0].id)
|
||||||
|
|
||||||
|
@mock.patch("rally.plugins.openstack.wrappers.glance.wrap")
|
||||||
|
def test__discover_or_create_image(self, mock_wrap):
|
||||||
|
client = mock_wrap.return_value
|
||||||
|
|
||||||
image = self.context._discover_or_create_image()
|
image = self.context._discover_or_create_image()
|
||||||
self.assertEqual("id1", image.id)
|
self.assertEqual(image, client.create_image.return_value)
|
||||||
self.assertEqual("id1", self.context._created_images[0].id)
|
self.assertEqual(self.context._created_images[0],
|
||||||
|
client.create_image.return_value)
|
||||||
|
mock_wrap.assert_called_once_with(self.context.clients.glance,
|
||||||
|
self.context)
|
||||||
|
client.create_image.assert_called_once_with(
|
||||||
|
container_format=CONF.image.container_format,
|
||||||
|
image_location=mock.ANY,
|
||||||
|
disk_format=CONF.image.disk_format,
|
||||||
|
name=mock.ANY,
|
||||||
|
is_public=True)
|
||||||
|
|
||||||
def test__create_flavor(self):
|
def test__create_flavor(self):
|
||||||
client = self.context.clients.nova()
|
client = self.context.clients.nova()
|
||||||
|
Loading…
Reference in New Issue
Block a user