Introduce an image driver interface
With this image driver interface, we align our codebase with other existing driver interfaces like compute, network and volume. This interface also allows the amphora provider driver to check for existence of tagged images at API level (e.g. amphora image tag capability in Octavia flavors). Change-Id: Id808c082808fafe1a1e004957ff47eca57f97ee8
This commit is contained in:
parent
4a4a2344de
commit
a422e5a203
@ -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
|
||||
|
@ -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}}
|
||||
|
@ -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
|
||||
#
|
||||
|
@ -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')),
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
0
octavia/image/__init__.py
Normal file
0
octavia/image/__init__.py
Normal file
0
octavia/image/drivers/__init__.py
Normal file
0
octavia/image/drivers/__init__.py
Normal file
69
octavia/image/drivers/glance_driver.py
Normal file
69
octavia/image/drivers/glance_driver.py
Normal file
@ -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
|
0
octavia/image/drivers/noop_driver/__init__.py
Normal file
0
octavia/image/drivers/noop_driver/__init__.py
Normal file
43
octavia/image/drivers/noop_driver/driver.py
Normal file
43
octavia/image/drivers/noop_driver/driver.py
Normal file
@ -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
|
28
octavia/image/image_base.py
Normal file
28
octavia/image/image_base.py
Normal file
@ -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
|
||||
"""
|
@ -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):
|
||||
|
0
octavia/tests/unit/image/__init__.py
Normal file
0
octavia/tests/unit/image/__init__.py
Normal file
0
octavia/tests/unit/image/drivers/__init__.py
Normal file
0
octavia/tests/unit/image/drivers/__init__.py
Normal file
65
octavia/tests/unit/image/drivers/test_glance_driver.py
Normal file
65
octavia/tests/unit/image/drivers/test_glance_driver.py
Normal file
@ -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])
|
39
octavia/tests/unit/image/drivers/test_image_noop_driver.py
Normal file
39
octavia/tests/unit/image/drivers/test_image_noop_driver.py
Normal file
@ -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)
|
10
releasenotes/notes/add-c9b9401b831efb25.yaml
Normal file
10
releasenotes/notes/add-c9b9401b831efb25.yaml
Normal file
@ -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``.
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user