From fb53fe23402417d8b96106306d5360fdb7024524 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Thu, 25 Feb 2016 15:26:57 +0100 Subject: [PATCH] glance: support relying on tags to extract image id Deprecated amp_image_id option with the new amp_image_tag option. Also switched devstack plugin to rely on the tag to update the image used for new load balancers. Implements: blueprint use-glance-tags-to-manage-image Change-Id: Ibc28b2220565667e15ca2b2674e55074d6126ec3 --- devstack/plugin.sh | 5 +- devstack/settings | 1 + etc/octavia.conf | 18 ++- octavia/common/clients.py | 34 ++++ octavia/common/config.py | 23 +++ octavia/common/exceptions.py | 4 + octavia/compute/compute_base.py | 4 +- octavia/compute/drivers/noop_driver/driver.py | 24 +-- octavia/compute/drivers/nova_driver.py | 40 ++++- .../controller/worker/tasks/compute_tasks.py | 1 + octavia/tests/unit/common/test_clients.py | 40 +++++ .../drivers/test_compute_noop_driver.py | 8 +- .../unit/compute/drivers/test_nova_driver.py | 66 ++++++++ .../worker/tasks/test_compute_tasks.py | 6 + ...s-for-amphora-images-28bd9df1ed4b9ca3.yaml | 9 ++ requirements.txt | 1 + .../use_glance_tag_to_refer_to_image.rst | 147 ++++++++++++++++++ 17 files changed, 415 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/glance-tags-for-amphora-images-28bd9df1ed4b9ca3.yaml create mode 100644 specs/version1/use_glance_tag_to_refer_to_image.rst diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 2894163f75..13f1c1a544 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -177,7 +177,10 @@ function octavia_start { fi OCTAVIA_AMP_IMAGE_ID=$(glance image-list | grep ${OCTAVIA_AMP_IMAGE_NAME} | awk '{print $2}') - iniset $OCTAVIA_CONF controller_worker amp_image_id ${OCTAVIA_AMP_IMAGE_ID} + if [ -n "$OCTAVIA_AMP_IMAGE_ID" ]; then + glance image-tag-update ${OCTAVIA_AMP_IMAGE_ID} ${OCTAVIA_AMP_IMAGE_TAG} + fi + iniset $OCTAVIA_CONF controller_worker amp_image_tag ${OCTAVIA_AMP_IMAGE_TAG} create_amphora_flavor diff --git a/devstack/settings b/devstack/settings index 42d922af13..dbed33f6be 100644 --- a/devstack/settings +++ b/devstack/settings @@ -37,6 +37,7 @@ OCTAVIA_AMP_SSH_KEY_NAME=${OCTAVIA_AMP_SSH_KEY_NAME:-"octavia_ssh_key"} OCTAVIA_AMP_FLAVOR_ID=${OCTAVIA_AMP_FLAVOR_ID:-"10"} OCTAVIA_AMP_IMAGE_NAME=${OCTAVIA_AMP_IMAGE_NAME:-"amphora-x64-haproxy"} OCTAVIA_AMP_IMAGE_FILE=${OCTAVIA_AMP_IMAGE_FILE:-${OCTAVIA_DIR}/diskimage-create/${OCTAVIA_AMP_IMAGE_NAME}.qcow2} +OCTAVIA_AMP_IMAGE_TAG="amphora" OCTAVIA_HEALTH_KEY=${OCTAVIA_HEALTH_KEY:-"insecure"} diff --git a/etc/octavia.conf b/etc/octavia.conf index 125e9a92ba..9e8e4a8f25 100644 --- a/etc/octavia.conf +++ b/etc/octavia.conf @@ -132,9 +132,12 @@ [controller_worker] # amp_active_retries = 10 # amp_active_wait_sec = 10 +# Glance parameters to extract image ID to use for amphora. Only one of +# parameters is needed. Using tags is the recommended way to refer to images. +# amp_image_id = +# amp_image_tag = # Nova parameters to use when booting amphora # amp_flavor_id = -# amp_image_id = # amp_ssh_key_name = # amp_ssh_allowed_access = True # amp_network = @@ -218,6 +221,19 @@ # vrrp_garp_refresh_interval = 5 # vrrp_garp_refresh_count = 2 +[glance] +# The name of the glance service in the keystone catalog +# service_name = +# Custom glance endpoint if override is necessary +# endpoint = + +# Region in Identity service catalog to use for communication with the OpenStack services. +# region_name = + +# Endpoint type in Identity service catalog to use for communication with +# the OpenStack services. +# endpoint_type = publicURL + [nova] # The name of the nova service in the keystone catalog # service_name = diff --git a/octavia/common/clients.py b/octavia/common/clients.py index 4e938039ab..307a262613 100644 --- a/octavia/common/clients.py +++ b/octavia/common/clients.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from glanceclient import client as glance_client from neutronclient.neutron import client as neutron_client from novaclient import client as nova_client from oslo_log import log as logging @@ -19,6 +20,7 @@ from octavia.common import keystone from octavia.i18n import _LE LOG = logging.getLogger(__name__) +GLANCE_VERSION = '2' NEUTRON_VERSION = '2.0' NOVA_VERSION = '2' @@ -85,3 +87,35 @@ class NeutronAuth(object): with excutils.save_and_reraise_exception(): LOG.exception(_LE("Error creating Neutron client.")) return cls.neutron_client + + +class GlanceAuth(object): + glance_client = None + + @classmethod + def get_glance_client(cls, region, service_name=None, endpoint=None, + endpoint_type='publicURL'): + """Create glance client object. + + :param region: The region of the service + :param service_name: The name of the glance service in the catalog + :param endpoint: The endpoint of the service + :param endpoint_type: The endpoint_type of the service + :return: a Glance Client object. + :raises Exception: if the client cannot be created + """ + if not cls.glance_client: + kwargs = {'region_name': region, + 'session': keystone.get_session(), + 'interface': endpoint_type} + if service_name: + kwargs['service_name'] = service_name + if endpoint: + kwargs['endpoint'] = endpoint + try: + cls.glance_client = glance_client.Client( + GLANCE_VERSION, **kwargs) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("Error creating Glance client.")) + return cls.glance_client diff --git a/octavia/common/config.py b/octavia/common/config.py index 7f3997bcdc..d50c670902 100644 --- a/octavia/common/config.py +++ b/octavia/common/config.py @@ -210,8 +210,16 @@ controller_worker_opts = [ cfg.StrOpt('amp_flavor_id', default='', help=_('Nova instance flavor id for the Amphora')), + cfg.StrOpt('amp_image_tag', + default='', + help=_('Glance image tag for the Amphora image to boot. ' + 'Use this option to be able to update the image ' + 'without reconfiguring Octavia. ' + 'Ignored if amp_image_id is defined.')), cfg.StrOpt('amp_image_id', default='', + deprecated_for_removal=True, + deprecated_reason='Superseded by amp_image_tag option.', help=_('Glance image id for the Amphora image to boot')), cfg.StrOpt('amp_ssh_key_name', default='', @@ -377,6 +385,20 @@ neutron_opts = [ help=_('Endpoint interface in identity service to use')), ] +glance_opts = [ + cfg.StrOpt('service_name', + help=_('The name of the glance service in the ' + 'keystone catalog')), + cfg.StrOpt('endpoint', help=_('A new endpoint to override the endpoint ' + 'in the keystone catalog.')), + cfg.StrOpt('region_name', + help=_('Region in Identity service catalog to use for ' + 'communication with the OpenStack services.')), + cfg.StrOpt('endpoint_type', default='publicURL', + help=_('Endpoint interface in identity service to use')), +] + + # Register the configuration options cfg.CONF.register_opts(core_opts) cfg.CONF.register_opts(amphora_agent_opts, group='amphora_agent') @@ -396,6 +418,7 @@ cfg.CONF.import_group('keystone_authtoken', 'keystonemiddleware.auth_token') cfg.CONF.register_opts(keystone_authtoken_v3_opts, group='keystone_authtoken_v3') cfg.CONF.register_opts(nova_opts, group='nova') +cfg.CONF.register_opts(glance_opts, group='glance') cfg.CONF.register_opts(neutron_opts, group='neutron') diff --git a/octavia/common/exceptions.py b/octavia/common/exceptions.py index 0065b8b756..92e32ec5ab 100644 --- a/octavia/common/exceptions.py +++ b/octavia/common/exceptions.py @@ -174,6 +174,10 @@ class NoReadyAmphoraeException(OctaviaException): message = _LE('There are not any READY amphora available.') +class GlanceNoTaggedImages(OctaviaException): + message = _LE("No Glance images are tagged with %(tag)s tag.") + + class NoSuitableAmphoraException(OctaviaException): message = _LE('Unable to allocate an amphora due to: %(msg)s') diff --git a/octavia/compute/compute_base.py b/octavia/compute/compute_base.py index 68852a18fe..2327d59d19 100644 --- a/octavia/compute/compute_base.py +++ b/octavia/compute/compute_base.py @@ -21,7 +21,8 @@ import six class ComputeBase(object): @abc.abstractmethod - def build(self, name="amphora_name", amphora_flavor=None, image_id=None, + def build(self, name="amphora_name", amphora_flavor=None, + image_id=None, image_tag=None, key_name=None, sec_groups=None, network_ids=None, config_drive_files=None, user_data=None, server_group_id=None): """Build a new amphora. @@ -29,6 +30,7 @@ class ComputeBase(object): :param name: Optional name for Amphora :param amphora_flavor: Optionally specify a flavor :param image_id: ID of the base image for the amphora instance + :param image_tag: tag of the base image for the amphora instance :param key_name: Optionally specify a keypair :param sec_groups: Optionally specify list of security groups :param network_ids: A list of network IDs to attach to the amphora diff --git a/octavia/compute/drivers/noop_driver/driver.py b/octavia/compute/drivers/noop_driver/driver.py index c29d90e7d2..24837f93c7 100644 --- a/octavia/compute/drivers/noop_driver/driver.py +++ b/octavia/compute/drivers/noop_driver/driver.py @@ -27,21 +27,23 @@ class NoopManager(object): super(NoopManager, self).__init__() self.computeconfig = {} - def build(self, name="amphora_name", amphora_flavor=None, image_id=None, + def build(self, name="amphora_name", amphora_flavor=None, + image_id=None, image_tag=None, key_name=None, sec_groups=None, network_ids=None, config_drive_files=None, user_data=None, port_ids=None, server_group_id=None): LOG.debug("Compute %s no-op, build name %s, amphora_flavor %s, " - "image_id %s, key_name %s, sec_groups %s, network_ids %s," - "config_drive_files %s, user_data %s, port_ids %s," - "server_group_id %s", - self.__class__.__name__, name, amphora_flavor, image_id, + "image_id %s, image_tag %s, key_name %s, sec_groups %s, " + "network_ids %s, config_drive_files %s, user_data %s, " + "port_ids %s, server_group_id %s", + self.__class__.__name__, + name, amphora_flavor, image_id, image_tag, key_name, sec_groups, network_ids, config_drive_files, user_data, port_ids, server_group_id) - self.computeconfig[(name, amphora_flavor, image_id, key_name, - user_data)] = ( + self.computeconfig[(name, amphora_flavor, image_id, image_tag, + key_name, user_data)] = ( name, amphora_flavor, - image_id, key_name, sec_groups, + image_id, image_tag, key_name, sec_groups, network_ids, config_drive_files, user_data, port_ids, 'build') compute_id = uuidutils.generate_uuid() @@ -84,12 +86,14 @@ class NoopComputeDriver(driver_base.ComputeBase): super(NoopComputeDriver, self).__init__() self.driver = NoopManager() - def build(self, name="amphora_name", amphora_flavor=None, image_id=None, + def build(self, name="amphora_name", amphora_flavor=None, + image_id=None, image_tag=None, key_name=None, sec_groups=None, network_ids=None, config_drive_files=None, user_data=None, port_ids=None, server_group_id=None): - compute_id = self.driver.build(name, amphora_flavor, image_id, + compute_id = self.driver.build(name, amphora_flavor, + image_id, image_tag, key_name, sec_groups, network_ids, config_drive_files, user_data, port_ids, server_group_id) diff --git a/octavia/compute/drivers/nova_driver.py b/octavia/compute/drivers/nova_driver.py index a9a40bcb30..e7cd0a7759 100644 --- a/octavia/compute/drivers/nova_driver.py +++ b/octavia/compute/drivers/nova_driver.py @@ -26,11 +26,40 @@ from octavia.i18n import _LE, _LW LOG = logging.getLogger(__name__) CONF = cfg.CONF +CONF.import_group('glance', 'octavia.common.config') CONF.import_group('keystone_authtoken', 'octavia.common.config') CONF.import_group('networking', 'octavia.common.config') CONF.import_group('nova', 'octavia.common.config') +def _extract_amp_image_id_by_tag(client, image_tag): + images = list(client.images.list( + filters={'tag': [image_tag]}, + sort='created_at')) + if not images: + raise exceptions.GlanceNoTaggedImages(tag=image_tag) + image_id = images[-1]['id'] + num_images = len(images) + if num_images > 1: + LOG.warn( + _LW("A single Glance image should be tagged with %(tag)s tag, " + "but %(num)d found. Using %(image_id)s."), + {'tag': image_tag, 'num': num_images, 'image_id': image_id} + ) + return image_id + + +def _get_image_uuid(client, image_id, image_tag): + if image_id: + if image_tag: + LOG.warn( + _LW("Both amp_image_id and amp_image_tag options defined. " + "Using the former.")) + return image_id + + return _extract_amp_image_id_by_tag(client, image_tag) + + class VirtualMachineManager(compute_base.ComputeBase): '''Compute implementation of virtual machines via nova.''' @@ -41,10 +70,16 @@ class VirtualMachineManager(compute_base.ComputeBase): endpoint=CONF.nova.endpoint, region=CONF.nova.region_name, endpoint_type=CONF.nova.endpoint_type) + self._glance_client = clients.GlanceAuth.get_glance_client( + service_name=CONF.glance.service_name, + endpoint=CONF.glance.endpoint, + region=CONF.glance.region_name, + endpoint_type=CONF.glance.endpoint_type) self.manager = self._nova_client.servers self.server_groups = self._nova_client.server_groups - def build(self, name="amphora_name", amphora_flavor=None, image_id=None, + def build(self, name="amphora_name", amphora_flavor=None, + image_id=None, image_tag=None, key_name=None, sec_groups=None, network_ids=None, port_ids=None, config_drive_files=None, user_data=None, server_group_id=None): @@ -53,6 +88,7 @@ class VirtualMachineManager(compute_base.ComputeBase): :param name: optional name for amphora :param amphora_flavor: image flavor for virtual machine :param image_id: image ID for virtual machine + :param image_tag: image tag for virtual machine :param key_name: keypair to add to the virtual machine :param sec_groups: Security group IDs for virtual machine :param network_ids: Network IDs to include on virtual machine @@ -84,6 +120,8 @@ class VirtualMachineManager(compute_base.ComputeBase): server_group = None if server_group_id is None else { "group": server_group_id} + image_id = _get_image_uuid( + self._glance_client, image_id, image_tag) amphora = self.manager.create( name=name, image=image_id, flavor=amphora_flavor, key_name=key_name, security_groups=sec_groups, diff --git a/octavia/controller/worker/tasks/compute_tasks.py b/octavia/controller/worker/tasks/compute_tasks.py index 88b71b1b07..fef33793ec 100644 --- a/octavia/controller/worker/tasks/compute_tasks.py +++ b/octavia/controller/worker/tasks/compute_tasks.py @@ -78,6 +78,7 @@ class ComputeCreate(BaseComputeTask): name="amphora-" + amphora_id, amphora_flavor=CONF.controller_worker.amp_flavor_id, image_id=CONF.controller_worker.amp_image_id, + image_tag=CONF.controller_worker.amp_image_tag, key_name=key_name, sec_groups=CONF.controller_worker.amp_secgroup_list, network_ids=[CONF.controller_worker.amp_network], diff --git a/octavia/tests/unit/common/test_clients.py b/octavia/tests/unit/common/test_clients.py index b8453ee0ec..6a7b48e0d8 100644 --- a/octavia/tests/unit/common/test_clients.py +++ b/octavia/tests/unit/common/test_clients.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import glanceclient.v2 import mock import neutronclient.v2_0 import novaclient.v2 @@ -98,3 +99,42 @@ class TestNeutronAuth(base.TestCase): region="test-region", service_name="neutronEndpoint1", endpoint="test-endpoint", endpoint_type='publicURL') self.assertIs(bc1, bc2) + + +class TestGlanceAuth(base.TestCase): + + def setUp(self): + CONF.set_override(group='keystone_authtoken', name='auth_version', + override='2', enforce_type=True) + # Reset the session and client + clients.GlanceAuth.glance_client = None + keystone._SESSION = None + + super(TestGlanceAuth, self).setUp() + + def test_get_glance_client(self): + # There should be no existing client + self.assertIsNone( + clients.GlanceAuth.glance_client + ) + + # Mock out the keystone session and get the client + keystone._SESSION = mock.MagicMock() + bc1 = clients.GlanceAuth.get_glance_client( + region=None, endpoint_type='publicURL') + + # Our returned client should also be the saved client + self.assertIsInstance( + clients.GlanceAuth.glance_client, + glanceclient.v2.client.Client + ) + self.assertIs( + clients.GlanceAuth.glance_client, + bc1 + ) + + # Getting the session again should return the same object + bc2 = clients.GlanceAuth.get_glance_client( + region="test-region", service_name="glanceEndpoint1", + endpoint="test-endpoint", endpoint_type='publicURL') + self.assertIs(bc1, bc2) diff --git a/octavia/tests/unit/compute/drivers/test_compute_noop_driver.py b/octavia/tests/unit/compute/drivers/test_compute_noop_driver.py index db2bdedd3a..5de0d1f774 100644 --- a/octavia/tests/unit/compute/drivers/test_compute_noop_driver.py +++ b/octavia/tests/unit/compute/drivers/test_compute_noop_driver.py @@ -30,6 +30,7 @@ class TestNoopComputeDriver(base.TestCase): self.name = "amphora_name" self.amphora_flavor = "m1.tiny" self.image_id = self.FAKE_UUID_2 + self.image_tag = "faketag" self.key_name = "key_name" self.sec_groups = "default" self.network_ids = self.FAKE_UUID_3 @@ -41,18 +42,21 @@ class TestNoopComputeDriver(base.TestCase): self.server_group_id = self.FAKE_UUID_1 def build(self): - self.driver.build(self.name, self.amphora_flavor, self.image_id, + self.driver.build(self.name, self.amphora_flavor, + self.image_id, self.image_tag, self.key_name, self.sec_groups, self.network_ids, self.confdrivefiles, self.user_data, self.server_group_id) - self.assertEqual((self.name, self.amphora_flavor, self.image_id, + self.assertEqual((self.name, self.amphora_flavor, + self.image_id, self.image_tag, self.key_name, self.sec_groups, self.network_ids, self.config_drive_files, self.user_data, self.server_group_id, 'build'), self.driver.driver.computeconfig[(self.name, self.amphora_flavor, self.image_id, + self.image_tag, self.key_name, self.sec_groups, self.network_ids, diff --git a/octavia/tests/unit/compute/drivers/test_nova_driver.py b/octavia/tests/unit/compute/drivers/test_nova_driver.py index 299cce1493..a7d9cc3dbe 100644 --- a/octavia/tests/unit/compute/drivers/test_nova_driver.py +++ b/octavia/tests/unit/compute/drivers/test_nova_driver.py @@ -17,6 +17,7 @@ from novaclient import exceptions as nova_exceptions from oslo_config import cfg from oslo_utils import uuidutils +from octavia.common import clients from octavia.common import constants from octavia.common import data_models as models from octavia.common import exceptions @@ -27,6 +28,62 @@ import octavia.tests.unit.base as base CONF = cfg.CONF +class Test_GetImageUuid(base.TestCase): + + def test__get_image_uuid_tag(self): + client = mock.Mock() + with mock.patch.object(nova_common, + '_extract_amp_image_id_by_tag', + return_value='fakeid') as extract: + image_id = nova_common._get_image_uuid(client, '', 'faketag') + self.assertEqual('fakeid', image_id) + extract.assert_called_with(client, 'faketag') + + def test__get_image_uuid_notag(self): + client = mock.Mock() + image_id = nova_common._get_image_uuid(client, 'fakeid', '') + self.assertEqual('fakeid', image_id) + + def test__get_image_uuid_id_beats_tag(self): + client = mock.Mock() + image_id = nova_common._get_image_uuid(client, 'fakeid', 'faketag') + self.assertEqual('fakeid', image_id) + + +class Test_ExtractAmpImageIdByTag(base.TestCase): + + def setUp(self): + super(Test_ExtractAmpImageIdByTag, self).setUp() + client_mock = mock.patch.object(clients.GlanceAuth, + 'get_glance_client') + self.client = client_mock.start().return_value + + def test_no_images(self): + self.client.images.list.return_value = [] + self.assertRaises( + exceptions.GlanceNoTaggedImages, + nova_common._extract_amp_image_id_by_tag, self.client, 'faketag') + + def test_single_image(self): + images = [ + {'id': uuidutils.generate_uuid(), 'tag': 'faketag'} + ] + self.client.images.list.return_value = images + image_id = nova_common._extract_amp_image_id_by_tag(self.client, + 'faketag') + self.assertIn(image_id, images[0]['id']) + + def test_multiple_images_returns_one_of_images(self): + images = [ + {'id': image_id, 'tag': 'faketag'} + for image_id in [uuidutils.generate_uuid() for i in range(10)] + ] + self.client.images.list.return_value = images + image_id = nova_common._extract_amp_image_id_by_tag(self.client, + 'faketag') + self.assertIn(image_id, [image['id'] for image in images]) + + class TestNovaClient(base.TestCase): def setUp(self): @@ -108,6 +165,15 @@ class TestNovaClient(base.TestCase): self.manager.manager.create.side_effect = Exception self.assertRaises(exceptions.ComputeBuildException, self.manager.build) + def test_build_extracts_image_id_by_tag(self): + expected_id = 'fakeid-by-tag' + with mock.patch.object(nova_common, '_get_image_uuid', + return_value=expected_id): + self.manager.build(image_id='fakeid', image_tag='tag') + + self.assertEqual(expected_id, + self.manager.manager.create.call_args[1]['image']) + def test_delete(self): amphora_id = self.manager.build(amphora_flavor=1, image_id=1, key_name=1, sec_groups=1, diff --git a/octavia/tests/unit/controller/worker/tasks/test_compute_tasks.py b/octavia/tests/unit/controller/worker/tasks/test_compute_tasks.py index 60bef3a31b..b72eac79ec 100644 --- a/octavia/tests/unit/controller/worker/tasks/test_compute_tasks.py +++ b/octavia/tests/unit/controller/worker/tasks/test_compute_tasks.py @@ -27,6 +27,7 @@ import octavia.tests.unit.base as base AMP_FLAVOR_ID = 10 AMP_IMAGE_ID = 11 +AMP_IMAGE_TAG = 'glance_tag' AMP_SSH_KEY_NAME = None AMP_NET = uuidutils.generate_uuid() AMP_SEC_GROUPS = [] @@ -62,6 +63,7 @@ class TestComputeTasks(base.TestCase): conf = oslo_fixture.Config(cfg.CONF) conf.config(group="controller_worker", amp_flavor_id=AMP_FLAVOR_ID) conf.config(group="controller_worker", amp_image_id=AMP_IMAGE_ID) + conf.config(group="controller_worker", amp_image_tag=AMP_IMAGE_TAG) conf.config(group="controller_worker", amp_ssh_key_name=AMP_SSH_KEY_NAME) conf.config(group="controller_worker", amp_network=AMP_NET) @@ -95,6 +97,7 @@ class TestComputeTasks(base.TestCase): name="amphora-" + _amphora_mock.id, amphora_flavor=AMP_FLAVOR_ID, image_id=AMP_IMAGE_ID, + image_tag=AMP_IMAGE_TAG, key_name=AMP_SSH_KEY_NAME, sec_groups=AMP_SEC_GROUPS, network_ids=[AMP_NET], @@ -154,6 +157,7 @@ class TestComputeTasks(base.TestCase): name="amphora-" + _amphora_mock.id, amphora_flavor=AMP_FLAVOR_ID, image_id=AMP_IMAGE_ID, + image_tag=AMP_IMAGE_TAG, key_name=AMP_SSH_KEY_NAME, sec_groups=AMP_SEC_GROUPS, network_ids=[AMP_NET], @@ -212,6 +216,7 @@ class TestComputeTasks(base.TestCase): name="amphora-" + _amphora_mock.id, amphora_flavor=AMP_FLAVOR_ID, image_id=AMP_IMAGE_ID, + image_tag=AMP_IMAGE_TAG, key_name=None, sec_groups=AMP_SEC_GROUPS, network_ids=[AMP_NET], @@ -268,6 +273,7 @@ class TestComputeTasks(base.TestCase): name="amphora-" + _amphora_mock.id, amphora_flavor=AMP_FLAVOR_ID, image_id=AMP_IMAGE_ID, + image_tag=AMP_IMAGE_TAG, key_name=AMP_SSH_KEY_NAME, sec_groups=AMP_SEC_GROUPS, network_ids=[AMP_NET], diff --git a/releasenotes/notes/glance-tags-for-amphora-images-28bd9df1ed4b9ca3.yaml b/releasenotes/notes/glance-tags-for-amphora-images-28bd9df1ed4b9ca3.yaml new file mode 100644 index 0000000000..2750e953f1 --- /dev/null +++ b/releasenotes/notes/glance-tags-for-amphora-images-28bd9df1ed4b9ca3.yaml @@ -0,0 +1,9 @@ +--- +features: + - Glance image containing the latest Amphora image can now be referenced + using a Glance tag. To use the feature, set amp_image_tag in + [controller_worker]. Note that amp_image_id should be unset for the new + feature to take into effect. +upgrade: + - amp_image_id option is deprecated and will be removed in one of the next + releases. Operators are adviced to migrate to the new amp_image_tag option. diff --git a/requirements.txt b/requirements.txt index 106bcf851d..ea39c5a755 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,6 +28,7 @@ oslo.service>=1.0.0 # Apache-2.0 oslo.utils>=3.5.0 # Apache-2.0 PyMySQL>=0.6.2 # MIT License python-barbicanclient>=3.3.0 # Apache-2.0 +python-glanceclient>=1.2.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 pyOpenSSL>=0.14 # Apache-2.0 WSME>=0.8 # MIT diff --git a/specs/version1/use_glance_tag_to_refer_to_image.rst b/specs/version1/use_glance_tag_to_refer_to_image.rst new file mode 100644 index 0000000000..0c8c3ba56e --- /dev/null +++ b/specs/version1/use_glance_tag_to_refer_to_image.rst @@ -0,0 +1,147 @@ +.. + This work is licensed under a Creative Commons Attribution 3.0 Unported + License. + + http://creativecommons.org/licenses/by/3.0/legalcode + +=============================================================== +Allow to use Glance image tag to refer to desired Amphora image +=============================================================== + +https://blueprints.launchpad.net/octavia/+spec/use-glance-tags-to-manage-image + +Currently, Octavia allows to define the Glance image ID to be used to boot new +Amphoras. This spec suggests another way to define the desired image, by using +Glance tagging mechanism. + + +Problem description +=================== + +The need to hardcode image ID in the service configuration file has drawbacks. + +Specifically, when an updated image is uploaded into Glance, the operator is +required to orchestrate configuration file update on all Octavia nodes and then +restart all Octavia workers to apply the change. It is both complex and error +prone. + + +Proposed change +=============== + +The spec suggests an alternative way to configure the desired Glance image to +be used for Octavia: using Glance image tagging feature. + +Glance allows to tag an image with any tag which is represented by a string +value. + +With the proposed change, Octavia operator will be able to tell Octavia to use +an image with the specified tag. Then Octavia will talk to Glance to determine +the exact image ID that is marked with the tag, before booting a new Amphora. + + +Alternatives +------------ + +Alternatively, we could make Nova talk to Glance to determine the desired image +ID based on the tag provided by Octavia. This approach is not supported by Nova +community because they don't want to impose the complexity into their code +base. + +Another alternative is to use image name instead of its ID. Nova is capable of +fetching the right image from Glance by name as long as the name is unique. +This is not optimal in case when the operator does not want to remove the old +Amphora image right after a new image is uploaded (for example, if the operator +wants to test the new image before cleaning up the old one). + +Data model impact +----------------- + +None. + +REST API impact +--------------- + +None. + +Security impact +--------------- + +Image tags should be managed by the same user that owns the images themselves. + +Notifications impact +-------------------- + +None. + +Other end user impact +--------------------- + +The proposed change should not break existing mechanism. To achieve that, the +new mechanism will be guarded with a new configuration option that will store +the desired Glance tag. + +Performance Impact +------------------ + +If the feature is used, Octavia will need to reach to Glance before booting a +new Amphora. The performance impact is well isolated and is not expected to be +significant. + +Other deployer impact +--------------------- + +The change couples Octavia with Glance. It should not be an issue since there +are no use cases to use Octavia without Glance installed. + +The new feature deprecates amp_image_id option. Operators that still use the +old image referencing mechanism will be adviced to switch to the new option. + +Eventually, the old mechanism will be removed from the tree. + +Developer impact +---------------- + +None. + +Implementation +============== + +Assignee(s) +----------- + +Primary assignee: + ihrachys (Ihar Hrachyshka) + +Work Items +---------- + +* introduce glanceclient integration into nova compute driver +* introduce new configuration option to store the glance tag +* introduce devstack plugin support to configure the feature +* provide documentation for the new feature + + +Dependencies +============ + +None. + +Testing +======= + +Unit tests will be written to cover the feature. + +Octavia plugin will be switched to using the new glance image referencing +mechanism. Tempest tests will be implemented to test the new feature. + + +Documentation Impact +==================== + +New feature should be documented in operator visible guides. + + +References +========== +