Merge pull request #54 from gnuoy/glance_tests
Add tests for testing the glance charm
This commit is contained in:
4
setup.py
4
setup.py
@@ -16,6 +16,10 @@ install_require = [
|
||||
'juju-wait',
|
||||
'PyYAML',
|
||||
'tenacity',
|
||||
'oslo.config',
|
||||
'python-keystoneclient',
|
||||
'python-novaclient',
|
||||
'python-neutronclient',
|
||||
]
|
||||
|
||||
tests_require = [
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import copy
|
||||
import mock
|
||||
import tenacity
|
||||
|
||||
import unit_tests.utils as ut_utils
|
||||
from zaza.utilities import openstack as openstack_utils
|
||||
|
||||
@@ -171,3 +173,157 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
|
||||
openstack_utils.get_undercloud_keystone_session()
|
||||
self.get_keystone_session.assert_called_once_with(_auth, scope=_scope)
|
||||
|
||||
def test_get_urllib_opener(self):
|
||||
self.patch_object(openstack_utils.urllib.request, "ProxyHandler")
|
||||
self.patch_object(openstack_utils.urllib.request, "HTTPHandler")
|
||||
self.patch_object(openstack_utils.urllib.request, "build_opener")
|
||||
self.patch_object(openstack_utils.os, "getenv")
|
||||
self.getenv.return_value = None
|
||||
HTTPHandler_mock = mock.MagicMock()
|
||||
self.HTTPHandler.return_value = HTTPHandler_mock
|
||||
openstack_utils.get_urllib_opener()
|
||||
self.build_opener.assert_called_once_with(HTTPHandler_mock)
|
||||
self.HTTPHandler.assert_called_once_with()
|
||||
|
||||
def test_get_urllib_opener_proxy(self):
|
||||
self.patch_object(openstack_utils.urllib.request, "ProxyHandler")
|
||||
self.patch_object(openstack_utils.urllib.request, "HTTPHandler")
|
||||
self.patch_object(openstack_utils.urllib.request, "build_opener")
|
||||
self.patch_object(openstack_utils.os, "getenv")
|
||||
self.getenv.return_value = 'http://squidy'
|
||||
ProxyHandler_mock = mock.MagicMock()
|
||||
self.ProxyHandler.return_value = ProxyHandler_mock
|
||||
openstack_utils.get_urllib_opener()
|
||||
self.build_opener.assert_called_once_with(ProxyHandler_mock)
|
||||
self.ProxyHandler.assert_called_once_with({'http': 'http://squidy'})
|
||||
|
||||
def test_find_cirros_image(self):
|
||||
urllib_opener_mock = mock.MagicMock()
|
||||
self.patch_object(openstack_utils, "get_urllib_opener")
|
||||
self.get_urllib_opener.return_value = urllib_opener_mock
|
||||
urllib_opener_mock.open().read.return_value = b'12'
|
||||
self.assertEqual(
|
||||
openstack_utils.find_cirros_image('aarch64'),
|
||||
'http://download.cirros-cloud.net/12/cirros-12-aarch64-disk.img')
|
||||
|
||||
def test_download_image(self):
|
||||
urllib_opener_mock = mock.MagicMock()
|
||||
self.patch_object(openstack_utils, "get_urllib_opener")
|
||||
self.get_urllib_opener.return_value = urllib_opener_mock
|
||||
self.patch_object(openstack_utils.urllib.request, "install_opener")
|
||||
self.patch_object(openstack_utils.urllib.request, "urlretrieve")
|
||||
openstack_utils.download_image('http://cirros/c.img', '/tmp/c1.img')
|
||||
self.install_opener.assert_called_once_with(urllib_opener_mock)
|
||||
self.urlretrieve.assert_called_once_with(
|
||||
'http://cirros/c.img', '/tmp/c1.img')
|
||||
|
||||
def test_resource_reaches_status(self):
|
||||
resource_mock = mock.MagicMock()
|
||||
resource_mock.get.return_value = mock.MagicMock(status='available')
|
||||
openstack_utils.resource_reaches_status(resource_mock, 'e01df65a')
|
||||
|
||||
def test_resource_reaches_status_fail(self):
|
||||
openstack_utils.resource_reaches_status.retry.wait = \
|
||||
tenacity.wait_none()
|
||||
resource_mock = mock.MagicMock()
|
||||
resource_mock.get.return_value = mock.MagicMock(status='unavailable')
|
||||
with self.assertRaises(AssertionError):
|
||||
openstack_utils.resource_reaches_status(
|
||||
resource_mock,
|
||||
'e01df65a')
|
||||
|
||||
def test_resource_reaches_status_bespoke(self):
|
||||
resource_mock = mock.MagicMock()
|
||||
resource_mock.get.return_value = mock.MagicMock(status='readyish')
|
||||
openstack_utils.resource_reaches_status(
|
||||
resource_mock,
|
||||
'e01df65a',
|
||||
'readyish')
|
||||
|
||||
def test_resource_reaches_status_bespoke_fail(self):
|
||||
openstack_utils.resource_reaches_status.retry.wait = \
|
||||
tenacity.wait_none()
|
||||
resource_mock = mock.MagicMock()
|
||||
resource_mock.get.return_value = mock.MagicMock(status='available')
|
||||
with self.assertRaises(AssertionError):
|
||||
openstack_utils.resource_reaches_status(
|
||||
resource_mock,
|
||||
'e01df65a',
|
||||
'readyish')
|
||||
|
||||
def test_resource_removed(self):
|
||||
resource_mock = mock.MagicMock()
|
||||
resource_mock.list.return_value = [mock.MagicMock(id='ba8204b0')]
|
||||
openstack_utils.resource_removed(resource_mock, 'e01df65a')
|
||||
|
||||
def test_resource_removed_fail(self):
|
||||
openstack_utils.resource_reaches_status.retry.wait = \
|
||||
tenacity.wait_none()
|
||||
resource_mock = mock.MagicMock()
|
||||
resource_mock.list.return_value = [mock.MagicMock(id='e01df65a')]
|
||||
with self.assertRaises(AssertionError):
|
||||
openstack_utils.resource_removed(resource_mock, 'e01df65a')
|
||||
|
||||
def test_delete_resource(self):
|
||||
resource_mock = mock.MagicMock()
|
||||
self.patch_object(openstack_utils, "resource_removed")
|
||||
openstack_utils.delete_resource(resource_mock, 'e01df65a')
|
||||
resource_mock.delete.assert_called_once_with('e01df65a')
|
||||
self.resource_removed.assert_called_once_with(
|
||||
resource_mock,
|
||||
'e01df65a',
|
||||
'resource')
|
||||
|
||||
def test_delete_image(self):
|
||||
self.patch_object(openstack_utils, "delete_resource")
|
||||
glance_mock = mock.MagicMock()
|
||||
openstack_utils.delete_image(glance_mock, 'b46c2d83')
|
||||
self.delete_resource.assert_called_once_with(
|
||||
glance_mock.images,
|
||||
'b46c2d83',
|
||||
msg="glance image")
|
||||
|
||||
def test_upload_image_to_glance(self):
|
||||
self.patch_object(openstack_utils, "resource_reaches_status")
|
||||
glance_mock = mock.MagicMock()
|
||||
image_mock = mock.MagicMock(id='9d1125af')
|
||||
glance_mock.images.create.return_value = image_mock
|
||||
m = mock.mock_open()
|
||||
with mock.patch('zaza.utilities.openstack.open', m, create=False) as f:
|
||||
openstack_utils.upload_image_to_glance(
|
||||
glance_mock,
|
||||
'/tmp/im1.img',
|
||||
'bob')
|
||||
glance_mock.images.create.assert_called_once_with(
|
||||
name='bob',
|
||||
disk_format='qcow2',
|
||||
visibility='public',
|
||||
container_format='bare')
|
||||
glance_mock.images.upload.assert_called_once_with(
|
||||
'9d1125af',
|
||||
f(),
|
||||
)
|
||||
self.resource_reaches_status.assert_called_once_with(
|
||||
glance_mock.images,
|
||||
'9d1125af',
|
||||
expected_status='active',
|
||||
msg='Image status wait')
|
||||
|
||||
def test_create_image(self):
|
||||
glance_mock = mock.MagicMock()
|
||||
self.patch_object(openstack_utils.os.path, "exists")
|
||||
self.patch_object(openstack_utils, "download_image")
|
||||
self.patch_object(openstack_utils, "upload_image_to_glance")
|
||||
openstack_utils.create_image(
|
||||
glance_mock,
|
||||
'http://cirros/c.img',
|
||||
'bob')
|
||||
self.exists.return_value = False
|
||||
self.download_image.assert_called_once_with(
|
||||
'http://cirros/c.img',
|
||||
'tests/c.img')
|
||||
self.upload_image_to_glance.assert_called_once_with(
|
||||
glance_mock,
|
||||
'tests/c.img',
|
||||
'bob')
|
||||
|
||||
0
zaza/charm_tests/glance/__init__.py
Normal file
0
zaza/charm_tests/glance/__init__.py
Normal file
7
zaza/charm_tests/glance/setup.py
Normal file
7
zaza/charm_tests/glance/setup.py
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
def basic_setup():
|
||||
"""Glance setup for testing glance is currently part of glance functional
|
||||
tests. Image setup for other tests to use should go here"""
|
||||
pass
|
||||
52
zaza/charm_tests/glance/tests.py
Normal file
52
zaza/charm_tests/glance/tests.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import logging
|
||||
|
||||
import zaza.utilities.openstack as openstack_utils
|
||||
import zaza.charm_tests.test_utils as test_utils
|
||||
|
||||
|
||||
class GlanceTest(test_utils.OpenStackBaseTest):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(GlanceTest, cls).setUpClass()
|
||||
cls.glance_client = openstack_utils.get_glance_session_client(
|
||||
cls.keystone_session)
|
||||
|
||||
def test_410_glance_image_create_delete(self):
|
||||
"""Create an image and then delete it"""
|
||||
image_url = openstack_utils.find_cirros_image(arch='x86_64')
|
||||
image = openstack_utils.create_image(
|
||||
self.glance_client,
|
||||
image_url,
|
||||
'cirrosimage')
|
||||
openstack_utils.delete_image(self.glance_client, image.id)
|
||||
|
||||
def test_411_set_disk_format(self):
|
||||
"""Change disk format and assert then change propagates to the correct
|
||||
file and that services are restarted as a result"""
|
||||
# Expected default and alternate values
|
||||
set_default = {
|
||||
'disk-formats': 'ami,ari,aki,vhd,vmdk,raw,qcow2,vdi,iso,root-tar'}
|
||||
set_alternate = {'disk-formats': 'qcow2'}
|
||||
|
||||
# Config file affected by juju set config change
|
||||
conf_file = '/etc/glance/glance-api.conf'
|
||||
|
||||
# Make config change, check for service restarts
|
||||
logging.debug('Setting disk format glance...')
|
||||
self.restart_on_changed(
|
||||
conf_file,
|
||||
set_default,
|
||||
set_alternate,
|
||||
{'image_format': {
|
||||
'disk_formats': [
|
||||
'ami,ari,aki,vhd,vmdk,raw,qcow2,vdi,iso,root-tar']}},
|
||||
{'image_format': {'disk_formats': ['qcow2']}},
|
||||
['glance-api'])
|
||||
|
||||
def test_901_pause_resume(self):
|
||||
"""Pause service and check services are stopped then resume and check
|
||||
they are started"""
|
||||
self.pause_resume(['glance-api'])
|
||||
@@ -1,13 +1,18 @@
|
||||
import logging
|
||||
import unittest
|
||||
import zaza.model
|
||||
|
||||
import zaza.charm_lifecycle.utils as utils
|
||||
import zaza.model as model
|
||||
import zaza.charm_lifecycle.utils as lifecycle_utils
|
||||
import zaza.utilities.openstack as openstack_utils
|
||||
|
||||
|
||||
def skipIfNotHA(service_name):
|
||||
def _skipIfNotHA_inner_1(f):
|
||||
def _skipIfNotHA_inner_2(*args, **kwargs):
|
||||
ips = zaza.model.get_app_ips(utils.get_juju_model(), service_name)
|
||||
ips = zaza.model.get_app_ips(
|
||||
lifecycle_utils.get_juju_model(),
|
||||
service_name)
|
||||
if len(ips) > 1:
|
||||
return f(*args, **kwargs)
|
||||
else:
|
||||
@@ -16,3 +21,135 @@ def skipIfNotHA(service_name):
|
||||
return _skipIfNotHA_inner_2
|
||||
|
||||
return _skipIfNotHA_inner_1
|
||||
|
||||
|
||||
class OpenStackBaseTest(unittest.TestCase):
|
||||
"""Generic helpers for testing OpenStack API charms"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
cls.model_name = lifecycle_utils.get_juju_model()
|
||||
cls.test_config = lifecycle_utils.get_charm_config()
|
||||
cls.application_name = cls.test_config['charm_name']
|
||||
cls.first_unit = model.get_first_unit_name(
|
||||
cls.model_name,
|
||||
cls.application_name)
|
||||
logging.debug('First unit is {}'.format(cls.first_unit))
|
||||
|
||||
def restart_on_changed(self, config_file, default_config, alternate_config,
|
||||
default_entry, alternate_entry, services):
|
||||
"""Test that changing config results in config file being updates and
|
||||
services restarted. Return config to default_config afterwards
|
||||
|
||||
:param config_file: Config file to check for settings
|
||||
:type config_file: str
|
||||
:param default_config: Dict of charm settings to set on completion
|
||||
:type default_config: dict
|
||||
:param alternate_config: Dict of charm settings to change to
|
||||
:type alternate_config: dict
|
||||
:param default_entry: Config file entries that correspond to
|
||||
default_config
|
||||
:type default_entry: dict
|
||||
:param alternate_entry: Config file entries that correspond to
|
||||
alternate_config
|
||||
:type alternate_entry: dict
|
||||
:param services: Services expected to be restarted when config_file is
|
||||
changed.
|
||||
:type services: list
|
||||
"""
|
||||
# first_unit is only useed to grab a timestamp, the assumption being
|
||||
# that all the units times are in sync.
|
||||
|
||||
mtime = model.get_unit_time(self.model_name, self.first_unit)
|
||||
logging.debug('Remote unit timestamp {}'.format(mtime))
|
||||
|
||||
logging.debug('Changing charm setting to {}'.format(alternate_config))
|
||||
model.set_application_config(
|
||||
self.model_name,
|
||||
self.application_name,
|
||||
alternate_config)
|
||||
|
||||
logging.debug(
|
||||
'Waiting for updates to propagate to {}'.format(config_file))
|
||||
model.block_until_oslo_config_entries_match(
|
||||
self.model_name,
|
||||
self.application_name,
|
||||
config_file,
|
||||
alternate_entry)
|
||||
|
||||
logging.debug(
|
||||
'Waiting for units to reach target states'.format(config_file))
|
||||
model.wait_for_application_states(
|
||||
self.model_name,
|
||||
self.test_config.get('target_deploy_status', {}))
|
||||
|
||||
# Config update has occured and hooks are idle. Any services should
|
||||
# have been restarted by now:
|
||||
logging.debug(
|
||||
'Waiting for services ({}) to be restarted'.format(services))
|
||||
model.block_until_services_restarted(
|
||||
self.model_name,
|
||||
self.application_name,
|
||||
mtime,
|
||||
services)
|
||||
|
||||
logging.debug('Restoring charm setting to {}'.format(default_config))
|
||||
model.set_application_config(
|
||||
self.model_name,
|
||||
self.application_name,
|
||||
default_config)
|
||||
|
||||
logging.debug(
|
||||
'Waiting for updates to propagate to '.format(config_file))
|
||||
model.block_until_oslo_config_entries_match(
|
||||
self.model_name,
|
||||
self.application_name,
|
||||
config_file,
|
||||
default_entry)
|
||||
|
||||
logging.debug(
|
||||
'Waiting for units to reach target states'.format(config_file))
|
||||
model.wait_for_application_states(
|
||||
self.model_name,
|
||||
self.test_config.get('target_deploy_status', {}))
|
||||
|
||||
def pause_resume(self, services):
|
||||
"""Pause and then resume a unit checking that services are in the
|
||||
required state after each action
|
||||
|
||||
:param services: Services expected to be restarted when config_file is
|
||||
changed.
|
||||
:type services: list
|
||||
"""
|
||||
model.block_until_service_status(
|
||||
self.model_name,
|
||||
self.first_unit,
|
||||
services,
|
||||
'running')
|
||||
model.block_until_unit_wl_status(
|
||||
self.model_name,
|
||||
self.first_unit,
|
||||
'active')
|
||||
model.run_action(self.model_name, self.first_unit, 'pause', {})
|
||||
model.block_until_unit_wl_status(
|
||||
self.model_name,
|
||||
self.first_unit,
|
||||
'maintenance')
|
||||
model.block_until_all_units_idle(self.model_name)
|
||||
model.block_until_service_status(
|
||||
self.model_name,
|
||||
self.first_unit,
|
||||
services,
|
||||
'stopped')
|
||||
model.run_action(self.model_name, self.first_unit, 'resume', {})
|
||||
model.block_until_unit_wl_status(
|
||||
self.model_name,
|
||||
self.first_unit,
|
||||
'active')
|
||||
model.block_until_all_units_idle(self.model_name)
|
||||
model.block_until_service_status(
|
||||
self.model_name,
|
||||
self.first_unit,
|
||||
services,
|
||||
'running')
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from .os_versions import (
|
||||
OPENSTACK_CODENAMES,
|
||||
SWIFT_CODENAMES,
|
||||
PACKAGE_CODENAMES,
|
||||
)
|
||||
|
||||
from glanceclient import Client as GlanceClient
|
||||
|
||||
from keystoneclient.v3 import client as keystoneclient_v3
|
||||
from keystoneauth1 import session
|
||||
from keystoneauth1.identity import (
|
||||
@@ -16,12 +16,14 @@ from novaclient import client as novaclient_client
|
||||
from neutronclient.v2_0 import client as neutronclient
|
||||
from neutronclient.common import exceptions as neutronexceptions
|
||||
|
||||
import juju_wait
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import six
|
||||
import sys
|
||||
import juju_wait
|
||||
import tenacity
|
||||
import urllib
|
||||
|
||||
from zaza import model
|
||||
from zaza.charm_lifecycle import utils as lifecycle_utils
|
||||
@@ -31,6 +33,9 @@ from zaza.utilities import (
|
||||
juju as juju_utils,
|
||||
)
|
||||
|
||||
CIRROS_RELEASE_URL = 'http://download.cirros-cloud.net/version/released'
|
||||
CIRROS_IMAGE_URL = 'http://download.cirros-cloud.net'
|
||||
|
||||
CHARM_TYPES = {
|
||||
'neutron': {
|
||||
'pkg': 'neutron-common',
|
||||
@@ -116,6 +121,17 @@ def get_ks_creds(cloud_creds, scope='PROJECT'):
|
||||
return auth
|
||||
|
||||
|
||||
def get_glance_session_client(session):
|
||||
"""Return glanceclient authenticated by keystone session
|
||||
|
||||
:param session: Keystone session object
|
||||
:type session: keystoneauth1.session.Session object
|
||||
:returns: Authenticated glanceclient
|
||||
:rtype: glanceclient.Client
|
||||
"""
|
||||
return GlanceClient('2', session=session)
|
||||
|
||||
|
||||
def get_nova_session_client(session):
|
||||
"""Return novaclient authenticated by keystone session
|
||||
|
||||
@@ -1269,3 +1285,190 @@ def get_overcloud_auth():
|
||||
'API_VERSION': 3,
|
||||
}
|
||||
return auth_settings
|
||||
|
||||
|
||||
def get_urllib_opener():
|
||||
"""Create a urllib opener taking into account proxy settings
|
||||
|
||||
Using urllib.request.urlopen will automatically handle proxies so none
|
||||
of this function is needed except we are currently specifying proxies
|
||||
via AMULET_HTTP_PROXY rather than http_proxy so a ProxyHandler is needed
|
||||
explicitly stating the proxies.
|
||||
|
||||
:returns: An opener which opens URLs via BaseHandlers chained together
|
||||
:rtype: urllib.request.OpenerDirector
|
||||
"""
|
||||
http_proxy = os.getenv('AMULET_HTTP_PROXY')
|
||||
logging.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy))
|
||||
|
||||
if http_proxy:
|
||||
handler = urllib.request.ProxyHandler({'http': http_proxy})
|
||||
else:
|
||||
handler = urllib.request.HTTPHandler()
|
||||
return urllib.request.build_opener(handler)
|
||||
|
||||
|
||||
def find_cirros_image(arch):
|
||||
"""Return the url for the latest cirros image for the given architecture
|
||||
|
||||
:param arch: aarch64, arm, i386, x86_64 etc
|
||||
:type arch: str
|
||||
:returns: URL for latest cirros image
|
||||
:rtype: str
|
||||
"""
|
||||
opener = get_urllib_opener()
|
||||
f = opener.open(CIRROS_RELEASE_URL)
|
||||
version = f.read().strip().decode()
|
||||
cirros_img = 'cirros-{}-{}-disk.img'.format(version, arch)
|
||||
return '{}/{}/{}'.format(CIRROS_IMAGE_URL, version, cirros_img)
|
||||
|
||||
|
||||
def download_image(image_url, target_file):
|
||||
"""Download the image from the given url to the specified file
|
||||
|
||||
:param image_url: URL to download image from
|
||||
:type image_url: str
|
||||
:param target_file: Local file to savee image to
|
||||
:type target_file: str
|
||||
"""
|
||||
opener = get_urllib_opener()
|
||||
urllib.request.install_opener(opener)
|
||||
urllib.request.urlretrieve(image_url, target_file)
|
||||
|
||||
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
|
||||
reraise=True, stop=tenacity.stop_after_attempt(8))
|
||||
def resource_reaches_status(resource, resource_id,
|
||||
expected_status='available',
|
||||
msg='resource'):
|
||||
"""Wait for an openstack resources status to reach an expected status
|
||||
within a specified time. Useful to confirm that nova instances, cinder
|
||||
vols, snapshots, glance images, heat stacks and other resources
|
||||
eventually reach the expected status.
|
||||
|
||||
:param resource: pointer to os resource type, ex: heat_client.stacks
|
||||
:type resource: str
|
||||
:param resource_id: unique id for the openstack resource
|
||||
:type resource_id: str
|
||||
:param expected_status: status to expect resource to reach
|
||||
:type expected_status: str
|
||||
:param msg: text to identify purpose in logging
|
||||
:type msy: str
|
||||
:raises: AssertionError
|
||||
"""
|
||||
resource_status = resource.get(resource_id).status
|
||||
assert resource_status == expected_status, (
|
||||
"Resource in {} state, waiting for {}" .format(resource_status,
|
||||
expected_status,))
|
||||
|
||||
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
|
||||
reraise=True, stop=tenacity.stop_after_attempt(2))
|
||||
def resource_removed(resource, resource_id, msg="resource"):
|
||||
"""Wait for an openstack resource to no longer be present
|
||||
|
||||
:param resource: pointer to os resource type, ex: heat_client.stacks
|
||||
:type resource: str
|
||||
:param resource_id: unique id for the openstack resource
|
||||
:type resource_id: str
|
||||
:param msg: text to identify purpose in logging
|
||||
:type msy: str
|
||||
:raises: AssertionError
|
||||
"""
|
||||
matching = [r for r in resource.list() if r.id == resource_id]
|
||||
logging.debug("Resource {} still present".format(resource_id))
|
||||
assert len(matching) == 0, "Resource {} still present".format(resource_id)
|
||||
|
||||
|
||||
def delete_resource(resource, resource_id, msg="resource"):
|
||||
"""Delete an openstack resource, such as one instance, keypair,
|
||||
image, volume, stack, etc., and confirm deletion within max wait time.
|
||||
|
||||
:param resource: pointer to os resource type, ex:glance_client.images
|
||||
:type resource: str
|
||||
:param resource_id: unique name or id for the openstack resource
|
||||
:type resource_id: str
|
||||
:param msg: text to identify purpose in logging
|
||||
:type msg: str
|
||||
"""
|
||||
logging.debug('Deleting OpenStack resource '
|
||||
'{} ({})'.format(resource_id, msg))
|
||||
resource.delete(resource_id)
|
||||
resource_removed(resource, resource_id, msg)
|
||||
|
||||
|
||||
def delete_image(glance, img_id):
|
||||
"""Delete the given image from glance
|
||||
|
||||
:param glance: Authenticated glanceclient
|
||||
:type glance: glanceclient.Client
|
||||
:param img_id: unique name or id for the openstack resource
|
||||
:type img_id: str
|
||||
"""
|
||||
delete_resource(glance.images, img_id, msg="glance image")
|
||||
|
||||
|
||||
def upload_image_to_glance(glance, local_path, image_name, disk_format='qcow2',
|
||||
visibility='public', container_format='bare'):
|
||||
"""Upload the given image to glance and apply the given label
|
||||
|
||||
:param glance: Authenticated glanceclient
|
||||
:type glance: glanceclient.Client
|
||||
:param local_path: Path to local image
|
||||
:type local_path: str
|
||||
:param image_name: The label to give the image in glance
|
||||
:type image_name: str
|
||||
:param disk_format: The of the underlying disk image.
|
||||
:type disk_format: str
|
||||
:param visibility: Who can access image
|
||||
:type visibility: str (public, private, shared or community)
|
||||
:param container_format: Whether the virtual machine image is in a file
|
||||
format that also contains metadata about the
|
||||
actual virtual machine.
|
||||
:type container_format: str
|
||||
:returns: glance image pointer
|
||||
:rtype: glanceclient.common.utils.RequestIdProxy
|
||||
"""
|
||||
# Create glance image
|
||||
image = glance.images.create(
|
||||
name=image_name,
|
||||
disk_format=disk_format,
|
||||
visibility=visibility,
|
||||
container_format=container_format)
|
||||
glance.images.upload(image.id, open(local_path, 'rb'))
|
||||
|
||||
resource_reaches_status(
|
||||
glance.images,
|
||||
image.id,
|
||||
expected_status='active',
|
||||
msg='Image status wait')
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def create_image(glance, image_url, image_name, image_cache_dir='tests'):
|
||||
"""Download the latest cirros image and upload it to glance,
|
||||
validate and return a resource pointer.
|
||||
|
||||
:param glance: Authenticated glanceclient
|
||||
:type glance: glanceclient.Client
|
||||
:param image_url: URL to download image from
|
||||
:type image_url: str
|
||||
:param image_name: display name for new image
|
||||
:type image_name: str
|
||||
:param image_cache_dir: Directory to store image in before uploading
|
||||
:type image_cache_dir: str
|
||||
:returns: glance image pointer
|
||||
:rtype: glanceclient.common.utils.RequestIdProxy
|
||||
"""
|
||||
logging.debug('Creating glance cirros image '
|
||||
'({})...'.format(image_name))
|
||||
|
||||
img_name = os.path.basename(urllib.parse.urlparse(image_url).path)
|
||||
local_path = os.path.join(image_cache_dir, img_name)
|
||||
|
||||
if not os.path.exists(local_path):
|
||||
download_image(image_url, local_path)
|
||||
|
||||
image = upload_image_to_glance(glance, local_path, image_name)
|
||||
return image
|
||||
|
||||
Reference in New Issue
Block a user