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
This commit is contained in:
parent
ad84b40f42
commit
fb53fe2340
@ -177,7 +177,10 @@ function octavia_start {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
OCTAVIA_AMP_IMAGE_ID=$(glance image-list | grep ${OCTAVIA_AMP_IMAGE_NAME} | awk '{print $2}')
|
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
|
create_amphora_flavor
|
||||||
|
|
||||||
|
@ -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_FLAVOR_ID=${OCTAVIA_AMP_FLAVOR_ID:-"10"}
|
||||||
OCTAVIA_AMP_IMAGE_NAME=${OCTAVIA_AMP_IMAGE_NAME:-"amphora-x64-haproxy"}
|
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_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"}
|
OCTAVIA_HEALTH_KEY=${OCTAVIA_HEALTH_KEY:-"insecure"}
|
||||||
|
|
||||||
|
@ -132,9 +132,12 @@
|
|||||||
[controller_worker]
|
[controller_worker]
|
||||||
# amp_active_retries = 10
|
# amp_active_retries = 10
|
||||||
# amp_active_wait_sec = 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
|
# Nova parameters to use when booting amphora
|
||||||
# amp_flavor_id =
|
# amp_flavor_id =
|
||||||
# amp_image_id =
|
|
||||||
# amp_ssh_key_name =
|
# amp_ssh_key_name =
|
||||||
# amp_ssh_allowed_access = True
|
# amp_ssh_allowed_access = True
|
||||||
# amp_network =
|
# amp_network =
|
||||||
@ -218,6 +221,19 @@
|
|||||||
# vrrp_garp_refresh_interval = 5
|
# vrrp_garp_refresh_interval = 5
|
||||||
# vrrp_garp_refresh_count = 2
|
# 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]
|
[nova]
|
||||||
# The name of the nova service in the keystone catalog
|
# The name of the nova service in the keystone catalog
|
||||||
# service_name =
|
# service_name =
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from glanceclient import client as glance_client
|
||||||
from neutronclient.neutron import client as neutron_client
|
from neutronclient.neutron import client as neutron_client
|
||||||
from novaclient import client as nova_client
|
from novaclient import client as nova_client
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
@ -19,6 +20,7 @@ from octavia.common import keystone
|
|||||||
from octavia.i18n import _LE
|
from octavia.i18n import _LE
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
GLANCE_VERSION = '2'
|
||||||
NEUTRON_VERSION = '2.0'
|
NEUTRON_VERSION = '2.0'
|
||||||
NOVA_VERSION = '2'
|
NOVA_VERSION = '2'
|
||||||
|
|
||||||
@ -85,3 +87,35 @@ class NeutronAuth(object):
|
|||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
LOG.exception(_LE("Error creating Neutron client."))
|
LOG.exception(_LE("Error creating Neutron client."))
|
||||||
return cls.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
|
||||||
|
@ -210,8 +210,16 @@ controller_worker_opts = [
|
|||||||
cfg.StrOpt('amp_flavor_id',
|
cfg.StrOpt('amp_flavor_id',
|
||||||
default='',
|
default='',
|
||||||
help=_('Nova instance flavor id for the Amphora')),
|
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',
|
cfg.StrOpt('amp_image_id',
|
||||||
default='',
|
default='',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Superseded by amp_image_tag option.',
|
||||||
help=_('Glance image id for the Amphora image to boot')),
|
help=_('Glance image id for the Amphora image to boot')),
|
||||||
cfg.StrOpt('amp_ssh_key_name',
|
cfg.StrOpt('amp_ssh_key_name',
|
||||||
default='',
|
default='',
|
||||||
@ -377,6 +385,20 @@ neutron_opts = [
|
|||||||
help=_('Endpoint interface in identity service to use')),
|
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
|
# Register the configuration options
|
||||||
cfg.CONF.register_opts(core_opts)
|
cfg.CONF.register_opts(core_opts)
|
||||||
cfg.CONF.register_opts(amphora_agent_opts, group='amphora_agent')
|
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,
|
cfg.CONF.register_opts(keystone_authtoken_v3_opts,
|
||||||
group='keystone_authtoken_v3')
|
group='keystone_authtoken_v3')
|
||||||
cfg.CONF.register_opts(nova_opts, group='nova')
|
cfg.CONF.register_opts(nova_opts, group='nova')
|
||||||
|
cfg.CONF.register_opts(glance_opts, group='glance')
|
||||||
cfg.CONF.register_opts(neutron_opts, group='neutron')
|
cfg.CONF.register_opts(neutron_opts, group='neutron')
|
||||||
|
|
||||||
|
|
||||||
|
@ -174,6 +174,10 @@ class NoReadyAmphoraeException(OctaviaException):
|
|||||||
message = _LE('There are not any READY amphora available.')
|
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):
|
class NoSuitableAmphoraException(OctaviaException):
|
||||||
message = _LE('Unable to allocate an amphora due to: %(msg)s')
|
message = _LE('Unable to allocate an amphora due to: %(msg)s')
|
||||||
|
|
||||||
|
@ -21,7 +21,8 @@ import six
|
|||||||
class ComputeBase(object):
|
class ComputeBase(object):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@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,
|
key_name=None, sec_groups=None, network_ids=None,
|
||||||
config_drive_files=None, user_data=None, server_group_id=None):
|
config_drive_files=None, user_data=None, server_group_id=None):
|
||||||
"""Build a new amphora.
|
"""Build a new amphora.
|
||||||
@ -29,6 +30,7 @@ class ComputeBase(object):
|
|||||||
:param name: Optional name for Amphora
|
:param name: Optional name for Amphora
|
||||||
:param amphora_flavor: Optionally specify a flavor
|
:param amphora_flavor: Optionally specify a flavor
|
||||||
:param image_id: ID of the base image for the amphora instance
|
: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 key_name: Optionally specify a keypair
|
||||||
:param sec_groups: Optionally specify list of security groups
|
:param sec_groups: Optionally specify list of security groups
|
||||||
:param network_ids: A list of network IDs to attach to the amphora
|
:param network_ids: A list of network IDs to attach to the amphora
|
||||||
|
@ -27,21 +27,23 @@ class NoopManager(object):
|
|||||||
super(NoopManager, self).__init__()
|
super(NoopManager, self).__init__()
|
||||||
self.computeconfig = {}
|
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,
|
key_name=None, sec_groups=None, network_ids=None,
|
||||||
config_drive_files=None, user_data=None, port_ids=None,
|
config_drive_files=None, user_data=None, port_ids=None,
|
||||||
server_group_id=None):
|
server_group_id=None):
|
||||||
LOG.debug("Compute %s no-op, build name %s, amphora_flavor %s, "
|
LOG.debug("Compute %s no-op, build name %s, amphora_flavor %s, "
|
||||||
"image_id %s, key_name %s, sec_groups %s, network_ids %s,"
|
"image_id %s, image_tag %s, key_name %s, sec_groups %s, "
|
||||||
"config_drive_files %s, user_data %s, port_ids %s,"
|
"network_ids %s, config_drive_files %s, user_data %s, "
|
||||||
"server_group_id %s",
|
"port_ids %s, server_group_id %s",
|
||||||
self.__class__.__name__, name, amphora_flavor, image_id,
|
self.__class__.__name__,
|
||||||
|
name, amphora_flavor, image_id, image_tag,
|
||||||
key_name, sec_groups, network_ids, config_drive_files,
|
key_name, sec_groups, network_ids, config_drive_files,
|
||||||
user_data, port_ids, server_group_id)
|
user_data, port_ids, server_group_id)
|
||||||
self.computeconfig[(name, amphora_flavor, image_id, key_name,
|
self.computeconfig[(name, amphora_flavor, image_id, image_tag,
|
||||||
user_data)] = (
|
key_name, user_data)] = (
|
||||||
name, amphora_flavor,
|
name, amphora_flavor,
|
||||||
image_id, key_name, sec_groups,
|
image_id, image_tag, key_name, sec_groups,
|
||||||
network_ids, config_drive_files,
|
network_ids, config_drive_files,
|
||||||
user_data, port_ids, 'build')
|
user_data, port_ids, 'build')
|
||||||
compute_id = uuidutils.generate_uuid()
|
compute_id = uuidutils.generate_uuid()
|
||||||
@ -84,12 +86,14 @@ class NoopComputeDriver(driver_base.ComputeBase):
|
|||||||
super(NoopComputeDriver, self).__init__()
|
super(NoopComputeDriver, self).__init__()
|
||||||
self.driver = NoopManager()
|
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,
|
key_name=None, sec_groups=None, network_ids=None,
|
||||||
config_drive_files=None, user_data=None, port_ids=None,
|
config_drive_files=None, user_data=None, port_ids=None,
|
||||||
server_group_id=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,
|
key_name, sec_groups, network_ids,
|
||||||
config_drive_files, user_data, port_ids,
|
config_drive_files, user_data, port_ids,
|
||||||
server_group_id)
|
server_group_id)
|
||||||
|
@ -26,11 +26,40 @@ from octavia.i18n import _LE, _LW
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
CONF.import_group('glance', 'octavia.common.config')
|
||||||
CONF.import_group('keystone_authtoken', 'octavia.common.config')
|
CONF.import_group('keystone_authtoken', 'octavia.common.config')
|
||||||
CONF.import_group('networking', 'octavia.common.config')
|
CONF.import_group('networking', 'octavia.common.config')
|
||||||
CONF.import_group('nova', '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):
|
class VirtualMachineManager(compute_base.ComputeBase):
|
||||||
'''Compute implementation of virtual machines via nova.'''
|
'''Compute implementation of virtual machines via nova.'''
|
||||||
|
|
||||||
@ -41,10 +70,16 @@ class VirtualMachineManager(compute_base.ComputeBase):
|
|||||||
endpoint=CONF.nova.endpoint,
|
endpoint=CONF.nova.endpoint,
|
||||||
region=CONF.nova.region_name,
|
region=CONF.nova.region_name,
|
||||||
endpoint_type=CONF.nova.endpoint_type)
|
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.manager = self._nova_client.servers
|
||||||
self.server_groups = self._nova_client.server_groups
|
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,
|
key_name=None, sec_groups=None, network_ids=None,
|
||||||
port_ids=None, config_drive_files=None, user_data=None,
|
port_ids=None, config_drive_files=None, user_data=None,
|
||||||
server_group_id=None):
|
server_group_id=None):
|
||||||
@ -53,6 +88,7 @@ class VirtualMachineManager(compute_base.ComputeBase):
|
|||||||
:param name: optional name for amphora
|
:param name: optional name for amphora
|
||||||
:param amphora_flavor: image flavor for virtual machine
|
:param amphora_flavor: image flavor for virtual machine
|
||||||
:param image_id: image ID 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 key_name: keypair to add to the virtual machine
|
||||||
:param sec_groups: Security group IDs for virtual machine
|
:param sec_groups: Security group IDs for virtual machine
|
||||||
:param network_ids: Network IDs to include on 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 {
|
server_group = None if server_group_id is None else {
|
||||||
"group": server_group_id}
|
"group": server_group_id}
|
||||||
|
|
||||||
|
image_id = _get_image_uuid(
|
||||||
|
self._glance_client, image_id, image_tag)
|
||||||
amphora = self.manager.create(
|
amphora = self.manager.create(
|
||||||
name=name, image=image_id, flavor=amphora_flavor,
|
name=name, image=image_id, flavor=amphora_flavor,
|
||||||
key_name=key_name, security_groups=sec_groups,
|
key_name=key_name, security_groups=sec_groups,
|
||||||
|
@ -78,6 +78,7 @@ class ComputeCreate(BaseComputeTask):
|
|||||||
name="amphora-" + amphora_id,
|
name="amphora-" + amphora_id,
|
||||||
amphora_flavor=CONF.controller_worker.amp_flavor_id,
|
amphora_flavor=CONF.controller_worker.amp_flavor_id,
|
||||||
image_id=CONF.controller_worker.amp_image_id,
|
image_id=CONF.controller_worker.amp_image_id,
|
||||||
|
image_tag=CONF.controller_worker.amp_image_tag,
|
||||||
key_name=key_name,
|
key_name=key_name,
|
||||||
sec_groups=CONF.controller_worker.amp_secgroup_list,
|
sec_groups=CONF.controller_worker.amp_secgroup_list,
|
||||||
network_ids=[CONF.controller_worker.amp_network],
|
network_ids=[CONF.controller_worker.amp_network],
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import glanceclient.v2
|
||||||
import mock
|
import mock
|
||||||
import neutronclient.v2_0
|
import neutronclient.v2_0
|
||||||
import novaclient.v2
|
import novaclient.v2
|
||||||
@ -98,3 +99,42 @@ class TestNeutronAuth(base.TestCase):
|
|||||||
region="test-region", service_name="neutronEndpoint1",
|
region="test-region", service_name="neutronEndpoint1",
|
||||||
endpoint="test-endpoint", endpoint_type='publicURL')
|
endpoint="test-endpoint", endpoint_type='publicURL')
|
||||||
self.assertIs(bc1, bc2)
|
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)
|
||||||
|
@ -30,6 +30,7 @@ class TestNoopComputeDriver(base.TestCase):
|
|||||||
self.name = "amphora_name"
|
self.name = "amphora_name"
|
||||||
self.amphora_flavor = "m1.tiny"
|
self.amphora_flavor = "m1.tiny"
|
||||||
self.image_id = self.FAKE_UUID_2
|
self.image_id = self.FAKE_UUID_2
|
||||||
|
self.image_tag = "faketag"
|
||||||
self.key_name = "key_name"
|
self.key_name = "key_name"
|
||||||
self.sec_groups = "default"
|
self.sec_groups = "default"
|
||||||
self.network_ids = self.FAKE_UUID_3
|
self.network_ids = self.FAKE_UUID_3
|
||||||
@ -41,18 +42,21 @@ class TestNoopComputeDriver(base.TestCase):
|
|||||||
self.server_group_id = self.FAKE_UUID_1
|
self.server_group_id = self.FAKE_UUID_1
|
||||||
|
|
||||||
def build(self):
|
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.key_name, self.sec_groups, self.network_ids,
|
||||||
self.confdrivefiles, self.user_data,
|
self.confdrivefiles, self.user_data,
|
||||||
self.server_group_id)
|
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.key_name, self.sec_groups, self.network_ids,
|
||||||
self.config_drive_files, self.user_data,
|
self.config_drive_files, self.user_data,
|
||||||
self.server_group_id, 'build'),
|
self.server_group_id, 'build'),
|
||||||
self.driver.driver.computeconfig[(self.name,
|
self.driver.driver.computeconfig[(self.name,
|
||||||
self.amphora_flavor,
|
self.amphora_flavor,
|
||||||
self.image_id,
|
self.image_id,
|
||||||
|
self.image_tag,
|
||||||
self.key_name,
|
self.key_name,
|
||||||
self.sec_groups,
|
self.sec_groups,
|
||||||
self.network_ids,
|
self.network_ids,
|
||||||
|
@ -17,6 +17,7 @@ from novaclient import exceptions as nova_exceptions
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from octavia.common import clients
|
||||||
from octavia.common import constants
|
from octavia.common import constants
|
||||||
from octavia.common import data_models as models
|
from octavia.common import data_models as models
|
||||||
from octavia.common import exceptions
|
from octavia.common import exceptions
|
||||||
@ -27,6 +28,62 @@ import octavia.tests.unit.base as base
|
|||||||
CONF = cfg.CONF
|
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):
|
class TestNovaClient(base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -108,6 +165,15 @@ class TestNovaClient(base.TestCase):
|
|||||||
self.manager.manager.create.side_effect = Exception
|
self.manager.manager.create.side_effect = Exception
|
||||||
self.assertRaises(exceptions.ComputeBuildException, self.manager.build)
|
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):
|
def test_delete(self):
|
||||||
amphora_id = self.manager.build(amphora_flavor=1, image_id=1,
|
amphora_id = self.manager.build(amphora_flavor=1, image_id=1,
|
||||||
key_name=1, sec_groups=1,
|
key_name=1, sec_groups=1,
|
||||||
|
@ -27,6 +27,7 @@ import octavia.tests.unit.base as base
|
|||||||
|
|
||||||
AMP_FLAVOR_ID = 10
|
AMP_FLAVOR_ID = 10
|
||||||
AMP_IMAGE_ID = 11
|
AMP_IMAGE_ID = 11
|
||||||
|
AMP_IMAGE_TAG = 'glance_tag'
|
||||||
AMP_SSH_KEY_NAME = None
|
AMP_SSH_KEY_NAME = None
|
||||||
AMP_NET = uuidutils.generate_uuid()
|
AMP_NET = uuidutils.generate_uuid()
|
||||||
AMP_SEC_GROUPS = []
|
AMP_SEC_GROUPS = []
|
||||||
@ -62,6 +63,7 @@ class TestComputeTasks(base.TestCase):
|
|||||||
conf = oslo_fixture.Config(cfg.CONF)
|
conf = oslo_fixture.Config(cfg.CONF)
|
||||||
conf.config(group="controller_worker", amp_flavor_id=AMP_FLAVOR_ID)
|
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_id=AMP_IMAGE_ID)
|
||||||
|
conf.config(group="controller_worker", amp_image_tag=AMP_IMAGE_TAG)
|
||||||
conf.config(group="controller_worker",
|
conf.config(group="controller_worker",
|
||||||
amp_ssh_key_name=AMP_SSH_KEY_NAME)
|
amp_ssh_key_name=AMP_SSH_KEY_NAME)
|
||||||
conf.config(group="controller_worker", amp_network=AMP_NET)
|
conf.config(group="controller_worker", amp_network=AMP_NET)
|
||||||
@ -95,6 +97,7 @@ class TestComputeTasks(base.TestCase):
|
|||||||
name="amphora-" + _amphora_mock.id,
|
name="amphora-" + _amphora_mock.id,
|
||||||
amphora_flavor=AMP_FLAVOR_ID,
|
amphora_flavor=AMP_FLAVOR_ID,
|
||||||
image_id=AMP_IMAGE_ID,
|
image_id=AMP_IMAGE_ID,
|
||||||
|
image_tag=AMP_IMAGE_TAG,
|
||||||
key_name=AMP_SSH_KEY_NAME,
|
key_name=AMP_SSH_KEY_NAME,
|
||||||
sec_groups=AMP_SEC_GROUPS,
|
sec_groups=AMP_SEC_GROUPS,
|
||||||
network_ids=[AMP_NET],
|
network_ids=[AMP_NET],
|
||||||
@ -154,6 +157,7 @@ class TestComputeTasks(base.TestCase):
|
|||||||
name="amphora-" + _amphora_mock.id,
|
name="amphora-" + _amphora_mock.id,
|
||||||
amphora_flavor=AMP_FLAVOR_ID,
|
amphora_flavor=AMP_FLAVOR_ID,
|
||||||
image_id=AMP_IMAGE_ID,
|
image_id=AMP_IMAGE_ID,
|
||||||
|
image_tag=AMP_IMAGE_TAG,
|
||||||
key_name=AMP_SSH_KEY_NAME,
|
key_name=AMP_SSH_KEY_NAME,
|
||||||
sec_groups=AMP_SEC_GROUPS,
|
sec_groups=AMP_SEC_GROUPS,
|
||||||
network_ids=[AMP_NET],
|
network_ids=[AMP_NET],
|
||||||
@ -212,6 +216,7 @@ class TestComputeTasks(base.TestCase):
|
|||||||
name="amphora-" + _amphora_mock.id,
|
name="amphora-" + _amphora_mock.id,
|
||||||
amphora_flavor=AMP_FLAVOR_ID,
|
amphora_flavor=AMP_FLAVOR_ID,
|
||||||
image_id=AMP_IMAGE_ID,
|
image_id=AMP_IMAGE_ID,
|
||||||
|
image_tag=AMP_IMAGE_TAG,
|
||||||
key_name=None,
|
key_name=None,
|
||||||
sec_groups=AMP_SEC_GROUPS,
|
sec_groups=AMP_SEC_GROUPS,
|
||||||
network_ids=[AMP_NET],
|
network_ids=[AMP_NET],
|
||||||
@ -268,6 +273,7 @@ class TestComputeTasks(base.TestCase):
|
|||||||
name="amphora-" + _amphora_mock.id,
|
name="amphora-" + _amphora_mock.id,
|
||||||
amphora_flavor=AMP_FLAVOR_ID,
|
amphora_flavor=AMP_FLAVOR_ID,
|
||||||
image_id=AMP_IMAGE_ID,
|
image_id=AMP_IMAGE_ID,
|
||||||
|
image_tag=AMP_IMAGE_TAG,
|
||||||
key_name=AMP_SSH_KEY_NAME,
|
key_name=AMP_SSH_KEY_NAME,
|
||||||
sec_groups=AMP_SEC_GROUPS,
|
sec_groups=AMP_SEC_GROUPS,
|
||||||
network_ids=[AMP_NET],
|
network_ids=[AMP_NET],
|
||||||
|
@ -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.
|
@ -28,6 +28,7 @@ oslo.service>=1.0.0 # Apache-2.0
|
|||||||
oslo.utils>=3.5.0 # Apache-2.0
|
oslo.utils>=3.5.0 # Apache-2.0
|
||||||
PyMySQL>=0.6.2 # MIT License
|
PyMySQL>=0.6.2 # MIT License
|
||||||
python-barbicanclient>=3.3.0 # Apache-2.0
|
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
|
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
|
||||||
pyOpenSSL>=0.14 # Apache-2.0
|
pyOpenSSL>=0.14 # Apache-2.0
|
||||||
WSME>=0.8 # MIT
|
WSME>=0.8 # MIT
|
||||||
|
147
specs/version1/use_glance_tag_to_refer_to_image.rst
Normal file
147
specs/version1/use_glance_tag_to_refer_to_image.rst
Normal file
@ -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
|
||||||
|
==========
|
||||||
|
|
Loading…
Reference in New Issue
Block a user