charm-glance/hooks/glance_contexts.py
Gabriel Cocenza 96a2bb3af9 Add support for HAProxy L7 checks
This change add several configuration options to enable HTTP checks
to the HAProxy configuration, instead of the default TCP connection
checks (which continue to be the default)

Closes-Bug: #1880610
Change-Id: I81d7fd67029dd5025f95b41d788b03ce4b6038bb
2023-01-17 18:06:05 -03:00

452 lines
15 KiB
Python

# Copyright 2016 Canonical Ltd
#
# 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 charmhelpers.core.strutils import (
bytes_from_string
)
from charmhelpers.core.hookenv import (
is_relation_made,
relation_ids,
relation_get,
related_units,
service_name,
config,
log as juju_log,
ERROR,
WARNING
)
from charmhelpers.contrib.openstack.context import (
OSContextGenerator,
ApacheSSLContext as SSLContext,
BindHostContext,
VolumeAPIContext,
)
from charmhelpers.contrib.hahelpers.cluster import (
determine_apache_port,
determine_api_port,
https,
)
from charmhelpers.contrib.openstack.utils import (
os_release,
CompareOpenStackReleases,
)
class GlanceContext(OSContextGenerator):
def __call__(self):
ctxt = {
'disk_formats': config('disk-formats')
}
if config('container-formats'):
ctxt['container_formats'] = config('container-formats')
if config('filesystem-store-datadir'):
ctxt['filesystem_store_datadir'] = (
config('filesystem-store-datadir'))
image_size_cap = config('image-size-cap')
if image_size_cap:
try:
ctxt['image_size_cap'] = bytes_from_string(
image_size_cap.replace(' ', '').upper())
except (ValueError, KeyError):
juju_log('Unable to parse value for image-size-cap ({}), '
'see config.yaml for information about valid '
'formatting'.format(config('image-size-cap')),
level=ERROR)
raise
return ctxt
class GlancePolicyContext(OSContextGenerator):
"""This Context is only used from Ussuri onwards. At Ussuri, Glance
implemented policy-in-code, and thus didn't ship with a policy.json.
Therefore, the charm introduces a 'policy.yaml' file that is used to
provide the override here.
Note that this is separate from policy overrides as it's a charm config
option that has existed prior to its introduction.
Update *_image_location policy to restrict to admin role.
We do this unconditonally and keep a record of the original as installed by
the package.
"""
def __call__(self):
if config('restrict-image-location-operations'):
policy_value = 'role:admin'
else:
policy_value = ''
ctxt = {
"get_image_location": policy_value,
"set_image_location": policy_value,
"delete_image_location": policy_value,
}
return ctxt
class GlanceImageImportContext(OSContextGenerator):
def __call__(self):
ctxt = {}
if config('image-conversion'):
ctxt['image_conversion'] = config('image-conversion')
return ctxt
class CephGlanceContext(OSContextGenerator):
interfaces = ['ceph-glance']
def __call__(self):
"""Used to generate template context to be added to glance-api.conf in
the presence of a ceph relation.
"""
if not is_relation_made(relation="ceph",
keys="key"):
return {}
service = service_name()
if config('pool-type') == 'erasure-coded':
pool_name = (
config('ec-rbd-metadata-pool') or
"{}-metadata".format(config('rbd-pool-name') or
service)
)
else:
if config('rbd-pool-name'):
pool_name = config('rbd-pool-name')
else:
pool_name = service
return {
# pool created based on service name.
'rbd_pool': pool_name,
'rbd_user': service,
'expose_image_locations': config('expose-image-locations')
}
class ObjectStoreContext(OSContextGenerator):
interfaces = ['object-store']
def __call__(self):
"""Object store config.
Used to generate template context to be added to glance-api.conf in
the presence of a 'object-store' relation.
"""
if not relation_ids('object-store'):
return {}
return {
'swift_store': True,
}
class ExternalS3Context(OSContextGenerator):
required_config_keys = (
"s3-store-host",
"s3-store-access-key",
"s3-store-secret-key",
"s3-store-bucket",
)
def __call__(self):
try:
self.validate()
except ValueError:
# ValueError will be handled in assess_status and the charm status
# will be blocked there. We will return the empty context here not
# to block the template rendering itself.
return {}
if config("s3-store-host"):
ctxt = {
"s3_store_host": config("s3-store-host"),
"s3_store_access_key": config("s3-store-access-key"),
"s3_store_secret_key": config("s3-store-secret-key"),
"s3_store_bucket": config("s3-store-bucket"),
}
if config("expose-image-locations"):
juju_log("Forcibly overriding expose_image_locations "
"not to expose S3 credentials", level=WARNING)
ctxt["expose_image_locations"] = False
return ctxt
return {}
def validate(self):
required_values = [
config(key) for key in self.required_config_keys
]
if all(required_values):
# The S3 backend was once removed in Newton development cycle and
# added back in Ussuri cycle in Glance upstream. As we rely on
# python3-boto3 in the charm, don't enable the backend before
# Ussuri release.
_release = os_release("glance-common")
if not CompareOpenStackReleases(_release) >= "ussuri":
juju_log(
"Not enabling S3 backend: The charm supports S3 backed "
"only for Ussuri or later releases. Your release is "
"{}".format(_release),
level=ERROR,
)
raise ValueError("{} is not supported".format(_release))
elif any(required_values):
juju_log(
"Unable to use S3 backend without all required S3 options "
"defined. Missing keys: {}".format(
" ".join(
(k for k in self.required_config_keys if not config(k))
)
),
level=ERROR,
)
raise ValueError("Missing necessary config options")
class CinderStoreContext(OSContextGenerator):
interfaces = ['cinder-volume-service', 'storage-backend']
def __call__(self):
"""Cinder store config.
Used to generate template context to be added to glance-api.conf in
the presence of a 'cinder-volume-service' relation or in the
presence of a flag 'cinder-backend' in the 'storage-backend' relation.
"""
if relation_ids('cinder-volume-service'):
return {'cinder_store': True}
for rid in relation_ids('storage-backend'):
for unit in related_units(rid):
value = relation_get('cinder-backend', rid=rid, unit=unit)
# value is a boolean flag
return {'cinder_store': value}
return {}
class MultiBackendContext(OSContextGenerator):
def _get_ceph_config(self):
ceph_ctx = CephGlanceContext()()
if not ceph_ctx:
return
ctx = {
"rbd_store_chunk_size": 8,
"rbd_store_pool": ceph_ctx["rbd_pool"],
"rbd_store_user": ceph_ctx["rbd_user"],
"rados_connect_timeout": 0,
"rbd_store_ceph_conf": "/etc/ceph/ceph.conf",
}
return ctx
def _get_swift_config(self):
swift_ctx = ObjectStoreContext()()
if not swift_ctx or swift_ctx.get("swift_store", False) is False:
return
ctx = {
"default_swift_reference": "swift",
"swift_store_config_file": "/etc/glance/glance-swift.conf",
"swift_store_create_container_on_put": "true",
}
return ctx
def _get_s3_config(self):
s3_ctx = ExternalS3Context()()
if not s3_ctx:
return
ctx = {
"s3_store_host": s3_ctx["s3_store_host"],
"s3_store_access_key": s3_ctx["s3_store_access_key"],
"s3_store_secret_key": s3_ctx["s3_store_secret_key"],
"s3_store_bucket": s3_ctx["s3_store_bucket"],
}
return ctx
def _get_cinder_config(self):
cinder_ctx = CinderStoreContext()()
if not cinder_ctx or cinder_ctx.get("cinder_store", False) is False:
return
return cinder_ctx
def __call__(self):
ctxt = {
"enabled_backend_configs": {},
"enabled_backends": None,
"default_store_backend": None,
}
backends = []
local_fs = config('filesystem-store-datadir')
if local_fs:
backends.append("local:file")
ctxt["enabled_backend_configs"]["local"] = {
"filesystem_store_datadir": local_fs,
"store_description": "Local filesystem store",
}
ceph_ctx = self._get_ceph_config()
if ceph_ctx:
backends.append("ceph:rbd")
ctxt["enabled_backend_configs"]["ceph"] = ceph_ctx
ctxt["default_store_backend"] = "ceph"
swift_ctx = self._get_swift_config()
if swift_ctx:
backends.append("swift:swift")
ctxt["enabled_backend_configs"]["swift"] = swift_ctx
if not ctxt["default_store_backend"]:
ctxt["default_store_backend"] = "swift"
s3_ctx = self._get_s3_config()
if s3_ctx:
backends.append("s3:s3")
ctxt["enabled_backend_configs"]["s3"] = s3_ctx
if not ctxt["default_store_backend"]:
ctxt["default_store_backend"] = "s3"
cinder_ctx = self._get_cinder_config()
if cinder_ctx:
cinder_volume_types = config('cinder-volume-types')
volume_types_str = cinder_volume_types or 'cinder'
volume_types = volume_types_str.split(',')
default_backend = volume_types[0]
for volume_type in volume_types:
backends.append(volume_type+':cinder')
# Add backend cinder_volume_type if cinder-volume-types configured
# In case cinder-volume-types not configured in charm, glance-api
# backend cinder_volume_type should be left blank so that glance
# creates volume in cinder without specifying any volume type.
if cinder_volume_types:
for volume_type in volume_types:
ctxt['enabled_backend_configs'][volume_type] = {
'cinder_volume_type': volume_type,
'cinder_http_retries': config('cinder-http-retries'),
'cinder_state_transition_timeout': config(
'cinder-state-transition-timeout'),
}
else:
# default cinder volume type cinder
ctxt['enabled_backend_configs']['cinder'] = {
'cinder_http_retries': config('cinder-http-retries'),
'cinder_state_transition_timeout': config(
'cinder-state-transition-timeout'),
}
# Add internal endpoints if use-internal-endpoints set to true
if config('use-internal-endpoints'):
vol_api_ctxt = VolumeAPIContext('glance-common')()
volume_catalog_info = vol_api_ctxt['volume_catalog_info']
for volume_type in volume_types:
if 'volume_type' not in ctxt['enabled_backend_configs']:
ctxt['enabled_backend_configs'][volume_type] = {}
ctxt['enabled_backend_configs'][volume_type].update(
{'cinder_catalog_info': volume_catalog_info})
if not ctxt["default_store_backend"]:
ctxt["default_store_backend"] = default_backend
if local_fs and not ctxt["default_store_backend"]:
ctxt["default_store_backend"] = "local"
if len(backends) > 0:
ctxt["enabled_backends"] = ", ".join(backends)
return ctxt
class MultiStoreContext(OSContextGenerator):
def __call__(self):
stores = ['glance.store.filesystem.Store', 'glance.store.http.Store']
store_mapping = {
'ceph': 'glance.store.rbd.Store',
'object-store': 'glance.store.swift.Store',
}
for store_relation, store_type in store_mapping.items():
if relation_ids(store_relation):
stores.append(store_type)
_release = os_release('glance-common')
if ((relation_ids('cinder-volume-service') or
relation_ids('storage-backend')) and
CompareOpenStackReleases(_release) >= 'mitaka'):
# even if storage-backend is present with cinder-backend=False it
# means that glance should not store images in cinder by default
# but can read images from cinder.
stores.append('glance.store.cinder.Store')
stores.sort()
return {
'known_stores': ','.join(stores)
}
class HAProxyContext(OSContextGenerator):
interfaces = ['cluster']
def __call__(self):
'''Extends the main charmhelpers HAProxyContext with a port mapping
specific to this charm.
Also used to extend glance-api.conf context with correct bind_port
'''
service = 'glance_api'
haproxy_port = 9292
apache_port = determine_apache_port(9292, singlenode_mode=True)
api_port = determine_api_port(9292, singlenode_mode=True)
backend_options = {
service: [{
'option': 'httpchk GET /healthcheck',
'http-check': 'expect status 200',
}]
}
ctxt = {
'service_ports': {service: [haproxy_port, apache_port]},
'bind_port': api_port,
}
ctxt['backend_options'] = backend_options
ctxt['https'] = https()
return ctxt
class ApacheSSLContext(SSLContext):
interfaces = ['https']
external_ports = [9292]
service_namespace = 'glance'
def __call__(self):
return super(ApacheSSLContext, self).__call__()
class LoggingConfigContext(OSContextGenerator):
def __call__(self):
return {'debug': config('debug'), 'verbose': config('verbose')}
class GlanceIPv6Context(BindHostContext):
def __call__(self):
ctxt = super(GlanceIPv6Context, self).__call__()
if config('prefer-ipv6'):
ctxt['registry_host'] = '[::]'
else:
ctxt['registry_host'] = '0.0.0.0'
return ctxt