diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 435a0f367e..ae517a9f9b 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -307,6 +307,7 @@ function octavia_configure { iniset $OCTAVIA_CONF controller_worker compute_driver ${OCTAVIA_COMPUTE_DRIVER} iniset $OCTAVIA_CONF controller_worker volume_driver ${OCTAVIA_VOLUME_DRIVER} iniset $OCTAVIA_CONF controller_worker network_driver ${OCTAVIA_NETWORK_DRIVER} + iniset $OCTAVIA_CONF controller_worker image_driver ${OCTAVIA_IMAGE_DRIVER} iniset $OCTAVIA_CONF controller_worker amp_image_tag ${OCTAVIA_AMP_IMAGE_TAG} iniuncomment $OCTAVIA_CONF health_manager heartbeat_key diff --git a/devstack/settings b/devstack/settings index 8af15ea1fe..8233e44a96 100644 --- a/devstack/settings +++ b/devstack/settings @@ -21,6 +21,7 @@ OCTAVIA_AMPHORA_DRIVER=${OCTAVIA_AMPHORA_DRIVER:-"amphora_haproxy_rest_driver"} OCTAVIA_NETWORK_DRIVER=${OCTAVIA_NETWORK_DRIVER:-"allowed_address_pairs_driver"} OCTAVIA_COMPUTE_DRIVER=${OCTAVIA_COMPUTE_DRIVER:-"compute_nova_driver"} OCTAVIA_VOLUME_DRIVER=${OCTAVIA_VOLUME_DRIVER:-"volume_noop_driver"} +OCTAVIA_IMAGE_DRIVER=${OCTAVIA_IMAGE_DRIVER:-"image_glance_driver"} OCTAVIA_USERNAME=${OCTAVIA_ADMIN_USER:-"admin"} OCTAVIA_PASSWORD=${OCTAVIA_PASSWORD:-${ADMIN_PASSWORD}} diff --git a/etc/octavia.conf b/etc/octavia.conf index a9a27241eb..1eb862fbf7 100644 --- a/etc/octavia.conf +++ b/etc/octavia.conf @@ -331,6 +331,11 @@ # # volume_driver = volume_noop_driver # +# Image driver options are image_noop_driver +# image_glance_driver +# +# image_driver = image_glance_driver +# # Distributor driver options are distributor_noop_driver # single_VIP_amphora # diff --git a/octavia/common/config.py b/octavia/common/config.py index 747584a2ad..a2a05f63db 100644 --- a/octavia/common/config.py +++ b/octavia/common/config.py @@ -478,6 +478,10 @@ controller_worker_opts = [ default=constants.VOLUME_NOOP_DRIVER, choices=constants.SUPPORTED_VOLUME_DRIVERS, help=_('Name of the volume driver to use')), + cfg.StrOpt('image_driver', + default='image_glance_driver', + choices=constants.SUPPORTED_IMAGE_DRIVERS, + help=_('Name of the image driver to use')), cfg.StrOpt('distributor_driver', default='distributor_noop_driver', help=_('Name of the distributor driver to use')), diff --git a/octavia/common/constants.py b/octavia/common/constants.py index bdd05f6444..cbbf6411b9 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -811,6 +811,10 @@ L4_PROTOCOL_MAP = { PROTOCOL_UDP: PROTOCOL_UDP, } +# Image drivers +SUPPORTED_IMAGE_DRIVERS = ['image_noop_driver', + 'image_glance_driver'] + # Volume drivers VOLUME_NOOP_DRIVER = 'volume_noop_driver' SUPPORTED_VOLUME_DRIVERS = [VOLUME_NOOP_DRIVER, diff --git a/octavia/common/exceptions.py b/octavia/common/exceptions.py index 9eff1a2842..fca49bf300 100644 --- a/octavia/common/exceptions.py +++ b/octavia/common/exceptions.py @@ -232,8 +232,8 @@ class NoReadyAmphoraeException(OctaviaException): message = _('There are not any READY amphora available.') -class GlanceNoTaggedImages(OctaviaException): - message = _("No Glance images are tagged with %(tag)s tag.") +class ImageGetException(OctaviaException): + message = _('Failed to retrieve image with %(tag)s tag.') # This is an internal use exception for the taskflow work flow diff --git a/octavia/compute/drivers/nova_driver.py b/octavia/compute/drivers/nova_driver.py index f07d8d0178..dac9a28f10 100644 --- a/octavia/compute/drivers/nova_driver.py +++ b/octavia/compute/drivers/nova_driver.py @@ -31,32 +31,6 @@ LOG = logging.getLogger(__name__) CONF = cfg.CONF -def _extract_amp_image_id_by_tag(client, image_tag, image_owner): - if image_owner: - images = list(client.images.list( - filters={'tag': [image_tag], - 'owner': image_owner, - 'status': constants.GLANCE_IMAGE_ACTIVE}, - sort='created_at:desc', - limit=2)) - else: - images = list(client.images.list( - filters={'tag': [image_tag], - 'status': constants.GLANCE_IMAGE_ACTIVE}, - sort='created_at:desc', - limit=2)) - - if not images: - raise exceptions.GlanceNoTaggedImages(tag=image_tag) - image_id = images[0]['id'] - num_images = len(images) - if num_images > 1: - LOG.warning("A single Glance image should be tagged with %(tag)s tag, " - "but at least two were found. Using %(image_id)s.", - {'tag': image_tag, 'image_id': image_id}) - return image_id - - class VirtualMachineManager(compute_base.ComputeBase): '''Compute implementation of virtual machines via nova.''' @@ -69,13 +43,6 @@ class VirtualMachineManager(compute_base.ComputeBase): endpoint_type=CONF.nova.endpoint_type, insecure=CONF.nova.insecure, cacert=CONF.nova.ca_certificates_file) - 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, - insecure=CONF.glance.insecure, - cacert=CONF.glance.ca_certificates_file) self.manager = self._nova_client.servers self.server_groups = self._nova_client.server_groups self.flavor_manager = self._nova_client.flavors @@ -85,6 +52,11 @@ class VirtualMachineManager(compute_base.ComputeBase): name=CONF.controller_worker.volume_driver, invoke_on_load=True ).driver + self.image_driver = stevedore_driver.DriverManager( + namespace='octavia.image.drivers', + name=CONF.controller_worker.image_driver, + invoke_on_load=True + ).driver def build(self, name="amphora_name", amphora_flavor=None, image_tag=None, image_owner=None, key_name=None, sec_groups=None, @@ -132,8 +104,8 @@ class VirtualMachineManager(compute_base.ComputeBase): "group": server_group_id} az_name = availability_zone or CONF.nova.availability_zone - image_id = _extract_amp_image_id_by_tag( - self._glance_client, image_tag, image_owner) + image_id = self.image_driver.get_image_id_by_tag( + image_tag, image_owner) if CONF.nova.random_amphora_name_length: r = random.SystemRandom() diff --git a/octavia/image/__init__.py b/octavia/image/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/octavia/image/drivers/__init__.py b/octavia/image/drivers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/octavia/image/drivers/glance_driver.py b/octavia/image/drivers/glance_driver.py new file mode 100644 index 0000000000..2b77ce30dc --- /dev/null +++ b/octavia/image/drivers/glance_driver.py @@ -0,0 +1,69 @@ +# Copyright 2020 Red Hat, 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 oslo_config import cfg +from oslo_log import log as logging + +from octavia.common import clients +from octavia.common import constants +from octavia.common import exceptions +from octavia.image import image_base + +LOG = logging.getLogger(__name__) + +CONF = cfg.CONF + + +class ImageManager(image_base.ImageBase): + '''Image implementation of virtual machines via Glance.''' + + def __init__(self): + super().__init__() + # Must initialize glance api + 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, + insecure=CONF.glance.insecure, + cacert=CONF.glance.ca_certificates_file + ) + self.manager = self._glance_client.images + + def get_image_id_by_tag(self, image_tag, image_owner=None): + """Get image ID by image tag and owner + + :param image_tag: image tag + :param image_owner: optional image owner + :raises: ImageGetException if no images found with given tag + :return: image id + """ + filters = {'tag': [image_tag], + 'status': constants.GLANCE_IMAGE_ACTIVE} + if image_owner: + filters.update({'owner': image_owner}) + + images = list(self.manager.list( + filters=filters, sort='created_at:desc', limit=2)) + + if not images: + raise exceptions.ImageGetException(tag=image_tag) + image_id = images[0]['id'] + num_images = len(images) + if num_images > 1: + LOG.warning("A single Glance image should be tagged with %(tag)s " + "tag, but at least two were found. Using " + "%(image_id)s.", + {'tag': image_tag, 'image_id': image_id}) + return image_id diff --git a/octavia/image/drivers/noop_driver/__init__.py b/octavia/image/drivers/noop_driver/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/octavia/image/drivers/noop_driver/driver.py b/octavia/image/drivers/noop_driver/driver.py new file mode 100644 index 0000000000..10acf186ce --- /dev/null +++ b/octavia/image/drivers/noop_driver/driver.py @@ -0,0 +1,43 @@ +# Copyright 2020 Red Hat, 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 oslo_log import log as logging + +from octavia.image import image_base as driver_base + +LOG = logging.getLogger(__name__) + + +class NoopManager(object): + def __init__(self): + super().__init__() + self.imageconfig = {} + + def get_image_id_by_tag(self, image_tag, image_owner=None): + LOG.debug("Image %s no-op, get_image_id_by_tag image tag %s, " + "image owner %s", + self.__class__.__name__, image_tag, image_owner) + self.imageconfig[image_tag, image_owner] = ( + image_tag, image_owner, 'get_image_id_by_tag') + return 1 + + +class NoopImageDriver(driver_base.ImageBase): + def __init__(self): + super().__init__() + self.driver = NoopManager() + + def get_image_id_by_tag(self, image_tag, image_owner=None): + image_id = self.driver.get_image_id_by_tag(image_tag, image_owner) + return image_id diff --git a/octavia/image/image_base.py b/octavia/image/image_base.py new file mode 100644 index 0000000000..3bb0af0d1c --- /dev/null +++ b/octavia/image/image_base.py @@ -0,0 +1,28 @@ +# Copyright 2020 Red Hat, 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 + + +class ImageBase(object, metaclass=abc.ABCMeta): + + @abc.abstractmethod + def get_image_id_by_tag(self, image_tag, image_owner=None): + """Get image ID by image tag and owner. + + :param image_tag: image tag + :param image_owner: optional image owner + :raises: ImageGetException if no images found with given tag + :return: image id + """ diff --git a/octavia/tests/unit/compute/drivers/test_nova_driver.py b/octavia/tests/unit/compute/drivers/test_nova_driver.py index 5f45d392f3..f1604ff770 100644 --- a/octavia/tests/unit/compute/drivers/test_nova_driver.py +++ b/octavia/tests/unit/compute/drivers/test_nova_driver.py @@ -18,7 +18,6 @@ from oslo_config import cfg from oslo_config import fixture as oslo_fixture 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 @@ -29,41 +28,6 @@ import octavia.tests.unit.base as base CONF = cfg.CONF -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', None) - - 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', None) - 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', None) - self.assertIn(image_id, [image['id'] for image in images]) - - class TestNovaClient(base.TestCase): def setUp(self): @@ -72,6 +36,8 @@ class TestNovaClient(base.TestCase): self.net_name = "lb-mgmt-net" conf.config(group="controller_worker", amp_boot_network_list=['1', '2']) + conf.config(group="controller_worker", + image_driver='image_noop_driver') self.conf = conf self.fake_image_uuid = uuidutils.generate_uuid() @@ -135,12 +101,6 @@ class TestNovaClient(base.TestCase): self.flavor_id = uuidutils.generate_uuid() self.availability_zone = 'my_test_az' - self.mock_image_tag = mock.patch( - 'octavia.compute.drivers.nova_driver.' - '_extract_amp_image_id_by_tag').start() - self.mock_image_tag.return_value = 1 - self.addCleanup(self.mock_image_tag.stop) - super().setUp() def test_build(self): diff --git a/octavia/tests/unit/image/__init__.py b/octavia/tests/unit/image/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/octavia/tests/unit/image/drivers/__init__.py b/octavia/tests/unit/image/drivers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/octavia/tests/unit/image/drivers/test_glance_driver.py b/octavia/tests/unit/image/drivers/test_glance_driver.py new file mode 100644 index 0000000000..61defe2238 --- /dev/null +++ b/octavia/tests/unit/image/drivers/test_glance_driver.py @@ -0,0 +1,65 @@ +# Copyright 2020 Red Hat, 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 unittest import mock + +from oslo_utils import uuidutils + +from octavia.common import exceptions +import octavia.image.drivers.glance_driver as glance_common +import octavia.tests.unit.base as base + + +class TestGlanceClient(base.TestCase): + + def setUp(self): + self.manager = glance_common.ImageManager() + self.manager.manager = mock.MagicMock() + + super(TestGlanceClient, self).setUp() + + def test_no_images(self): + self.manager.manager.list.return_value = [] + self.assertRaises( + exceptions.ImageGetException, + self.manager.get_image_id_by_tag, 'faketag') + + def test_single_image(self): + images = [ + {'id': uuidutils.generate_uuid(), 'tag': 'faketag'} + ] + self.manager.manager.list.return_value = images + image_id = self.manager.get_image_id_by_tag('faketag', None) + self.assertEqual(image_id, images[0]['id']) + + def test_single_image_owner(self): + owner = uuidutils.generate_uuid() + images = [ + {'id': uuidutils.generate_uuid(), + 'tag': 'faketag', + 'owner': owner} + ] + self.manager.manager.list.return_value = images + image_id = self.manager.get_image_id_by_tag('faketag', owner) + self.assertEqual(image_id, images[0]['id']) + self.assertEqual(owner, images[0]['owner']) + + 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.manager.manager.list.return_value = images + image_id = self.manager.get_image_id_by_tag('faketag', None) + self.assertIn(image_id, [image['id'] for image in images]) diff --git a/octavia/tests/unit/image/drivers/test_image_noop_driver.py b/octavia/tests/unit/image/drivers/test_image_noop_driver.py new file mode 100644 index 0000000000..470758ee21 --- /dev/null +++ b/octavia/tests/unit/image/drivers/test_image_noop_driver.py @@ -0,0 +1,39 @@ +# Copyright 2020 Red Hat, 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 oslo_config import cfg +from oslo_utils import uuidutils + +from octavia.image.drivers.noop_driver import driver +import octavia.tests.unit.base as base + + +CONF = cfg.CONF + + +class TestNoopImageDriver(base.TestCase): + + def setUp(self): + super(TestNoopImageDriver, self).setUp() + self.driver = driver.NoopImageDriver() + + def test_get_image_id_by_tag(self): + image_tag = 'amphora' + image_owner = uuidutils.generate_uuid() + image_id = self.driver.get_image_id_by_tag(image_tag, image_owner) + self.assertEqual((image_tag, image_owner, 'get_image_id_by_tag'), + self.driver.driver.imageconfig[( + image_tag, image_owner + )]) + self.assertEqual(1, image_id) diff --git a/releasenotes/notes/add-c9b9401b831efb25.yaml b/releasenotes/notes/add-c9b9401b831efb25.yaml new file mode 100644 index 0000000000..dfb2813cca --- /dev/null +++ b/releasenotes/notes/add-c9b9401b831efb25.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Introduced an image driver interface. Supported drivers are noop and + Glance. +upgrade: + - | + When the amphora provider driver is enabled, operators need to set option + ``[controller_worker]/image_driver``. The default image driver is + ``image_glance_driver``. For testing could be used ``image_noop_driver``. diff --git a/setup.cfg b/setup.cfg index bb82ecd2ee..21beb41696 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,6 +82,9 @@ octavia.network.drivers = octavia.volume.drivers = volume_noop_driver = octavia.volume.drivers.noop_driver.driver:NoopVolumeDriver volume_cinder_driver = octavia.volume.drivers.cinder_driver:VolumeManager +octavia.image.drivers = + image_noop_driver = octavia.image.drivers.noop_driver.driver:NoopImageDriver + image_glance_driver = octavia.image.drivers.glance_driver:ImageManager octavia.distributor.drivers = distributor_noop_driver = octavia.distributor.drivers.noop_driver.driver:NoopDistributorDriver single_VIP_amphora = octavia.distributor.drivers.single_VIP_amphora.driver:SingleVIPAmpDistributorDriver