Allow enabling S3 object storage backend

S3 backend has been available (again) since Ussuri in the upstream
Glance. We will enable the backend with config options for an external
S3 storage information.

Co-authored-by: Connor Chamberlain <connor.chamberlain@canonical.com>
Co-authored-by: Vladimir Grevtsev <vladimir.grevtsev@canonical.com>
func-test-pr: https://github.com/openstack-charmers/zaza-openstack-tests/pull/574
Closes-Bug: #1919338
Change-Id: Id76a74cc6041b9c3364399254681138475f19935
This commit is contained in:
Nobuto Murata 2021-05-24 10:52:26 +09:00
parent b1cae3160a
commit a6dc972b09
6 changed files with 229 additions and 5 deletions

View File

@ -68,9 +68,9 @@ This configuration can be used to support Glance in HA/scale-out deployments.
### Object storage-backed storage
Glance can use Object storage as its storage backend. OpenStack Swift and Ceph
RADOS Gateway are supported, and both resulting configurations can be used to
support Glance in HA/scale-out deployments.
Glance can use Object storage as its storage backend. OpenStack Swift, Ceph
RADOS Gateway, and external S3 are supported, and all resulting configurations
can be used to support Glance in HA/scale-out deployments.
#### Swift
@ -99,6 +99,21 @@ ceph-radosgw application:
Proceed with the common group of commands from the Ceph scenario.
#### External S3
This S3 backend is supported for Ussuri release or later in the charm.
The step below assumes an external and pre-existing S3 compatible server
available.
S3 server information can be passed via charm config options.
juju config glance \
s3-store-host='http://my-object-storage.example.com:8080' \
s3-store-access-key='ACCESS_KEY' \
s3-store-secret-key='SECRET_KEY' \
s3-store-bucket='BUCKET_NAME'
### Local storage
Glance can simply use the storage available on the application unit's machine
@ -118,7 +133,7 @@ by using the `--store` option to the `glance` CLI client:
glance image-create --store <backend-name> ...
Otherwise, the default backend is determined by the following precedence order
of backend names: 'ceph', 'swift', and then 'local'.
of backend names: 'ceph', 'swift', 's3', and then 'local'.
> **Important**: The backend name of 'swift' denotes both object storage
solutions (i.e. Swift and Ceph RADOS Gateway).

View File

@ -531,3 +531,29 @@ options:
description: |
Enable conversion of all images to raw format during image upload,
only supported on stein or newer.
s3-store-host:
type: string
default:
description: |
Location where S3 server is listening. Can be a DNS name or an IP
address. The value must start with the schema (http:// or
https://). e.g., https://s3.ap-northeast-1.amazonaws.com or
http://my-object-storage.example.com:8080
.
NOTE: The S3 backend can be enabled only for Ussuri or later
releases with this charm.
s3-store-access-key:
type: string
default:
description: |
Access key for authenticating to the S3 storage server.
s3-store-secret-key:
type: string
default:
description: |
Secret key for authenticating to the S3 storage server.
s3-store-bucket:
type: string
default:
description: |
Bucket name where the glance images will be stored in the S3 server.

View File

@ -154,6 +154,67 @@ class ObjectStoreContext(OSContextGenerator):
}
class ExternalS3Context(OSContextGenerator):
required_config_keys = (
"s3-store-host",
"s3-store-access-key",
"s3-store-secret-key",
"s3-store-bucket",
)
def __init__(self):
self.required_values = [
config(key) for key in self.required_config_keys
]
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"),
}
return ctxt
return {}
def validate(self):
if all(self.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(self.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']
@ -199,6 +260,12 @@ class MultiBackendContext(OSContextGenerator):
}
return ctx
def _get_s3_config(self):
s3_ctx = ExternalS3Context()()
if not s3_ctx:
return
return s3_ctx
def __call__(self):
ctxt = {
"enabled_backend_configs": {},
@ -227,6 +294,13 @@ class MultiBackendContext(OSContextGenerator):
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"
if local_fs and not ctxt["default_store_backend"]:
ctxt["default_store_backend"] = "local"

View File

@ -107,6 +107,7 @@ PY3_PACKAGES = [
"python3-cinderclient",
"python3-os-brick",
"python3-oslo.rootwrap",
"python3-boto3",
]
VERSION_PACKAGE = 'glance-common'
@ -510,6 +511,13 @@ def check_optional_config_and_relations(configs):
if ('ceph' in configs.complete_contexts()
and not is_request_complete(get_ceph_request())):
return ('waiting', 'Ceph broker request incomplete')
try:
ext_s3 = glance_contexts.ExternalS3Context()
ext_s3.validate()
except ValueError as e:
return ('blocked', 'Invalid external S3 config: {}'.format(str(e)))
# return 'unknown' as the lowest priority to not clobber an existing
# status.
return "unknown", ""

View File

@ -7,12 +7,12 @@ comment:
smoke_bundles:
- full_run: bionic-train
gate_bundles:
- ceph: focal-ussuri
- xenial-mitaka
- bionic-queens
- bionic-stein
- bionic-train
- bionic-ussuri
- ceph: focal-ussuri
- ceph: focal-victoria
- ceph: groovy-victoria
dev_bundles:
@ -27,6 +27,7 @@ configure:
- zaza.openstack.charm_tests.keystone.setup.add_demo_user
- ceph:
- zaza.openstack.charm_tests.glance.setup.add_lts_image
- zaza.openstack.charm_tests.glance.setup.configure_external_s3_backend
- zaza.openstack.charm_tests.keystone.setup.add_demo_user
- full_run:
- zaza.openstack.charm_tests.glance.setup.add_cirros_image
@ -38,6 +39,7 @@ tests:
- ceph:
- zaza.openstack.charm_tests.glance.tests.GlanceTest
- zaza.openstack.charm_tests.glance.tests.GlanceCephRGWBackendTest
- zaza.openstack.charm_tests.glance.tests.GlanceExternalS3Test
- zaza.openstack.charm_tests.policyd.tests.GlanceTests
- zaza.openstack.charm_tests.ceph.tests.CheckPoolTypes
- zaza.openstack.charm_tests.ceph.tests.BlueStoreCompressionCharmOperation

View File

@ -27,6 +27,7 @@ TO_PATCH = [
'determine_apache_port',
'determine_api_port',
'os_release',
'juju_log',
]
@ -167,6 +168,67 @@ class TestGlanceContexts(CharmTestCase):
'rbd_user': service,
'expose_image_locations': True})
def test_external_s3_not_configured(self):
config = {
's3-store-host': '',
's3-store-access-key': '',
's3-store-secret-key': '',
's3-store-bucket': ''}
self.config.side_effect = lambda x: config[x]
self.assertEqual(contexts.ExternalS3Context()(), {})
def test_external_s3_partially_configured(self):
config = {}
self.config.side_effect = lambda x: config[x]
config = {
's3-store-host': 'host',
's3-store-access-key': '',
's3-store-secret-key': '',
's3-store-bucket': ''}
self.assertEqual(contexts.ExternalS3Context()(), {})
config = {
's3-store-host': '',
's3-store-access-key': 'key',
's3-store-secret-key': '',
's3-store-bucket': ''}
self.assertEqual(contexts.ExternalS3Context()(), {})
config = {
's3-store-host': '',
's3-store-access-key': '',
's3-store-secret-key': 'key',
's3-store-bucket': ''}
self.assertEqual(contexts.ExternalS3Context()(), {})
config = {
's3-store-host': '',
's3-store-access-key': '',
's3-store-secret-key': '',
's3-store-bucket': 'bucket'}
self.assertEqual(contexts.ExternalS3Context()(), {})
def test_external_s3_configured(self):
host_name = 'http://my-object-storage.example.com:8080'
access_key = 'my-access-key'
secret_key = 'my-secret-key'
bucket = 'my-bucket'
config = {
's3-store-host': host_name,
's3-store-access-key': access_key,
's3-store-secret-key': secret_key,
's3-store-bucket': bucket}
self.config.side_effect = lambda x: config[x]
expected_ctx = {
's3_store_host': host_name,
's3_store_access_key': access_key,
's3_store_secret_key': secret_key,
's3_store_bucket': bucket
}
self.os_release.return_value = 'train'
self.assertEqual(contexts.ExternalS3Context()(), {})
self.os_release.return_value = 'ussuri'
self.assertEqual(contexts.ExternalS3Context()(), expected_ctx)
def test_multistore_below_mitaka(self):
self.os_release.return_value = 'liberty'
self.relation_ids.return_value = ['random_rid']
@ -327,6 +389,43 @@ class TestGlanceContexts(CharmTestCase):
'default_store_backend': 'ceph',
})
def test_multi_backend_with_external_s3(self):
self.maxDiff = None
self.os_release.return_value = 'ussuri'
self.relation_ids.return_value = []
self.is_relation_made.return_value = False
data_dir = '/some/data/dir'
s3_host = 'http://my-object-storage.example.com:8080'
s3_access_key = 'my-access-key'
s3_secret_key = 'my-secret-key'
s3_bucket = 'my-bucket'
conf_dict = {
'filesystem-store-datadir': data_dir,
's3-store-host': s3_host,
's3-store-access-key': s3_access_key,
's3-store-secret-key': s3_secret_key,
's3-store-bucket': s3_bucket,
}
self.config.side_effect = lambda x: conf_dict.get(x)
self.assertEqual(
contexts.MultiBackendContext()(),
{
'enabled_backend_configs': {
'local': {
'filesystem_store_datadir': data_dir,
'store_description': 'Local filesystem store',
},
's3': {
"s3_store_host": s3_host,
"s3_store_access_key": s3_access_key,
"s3_store_secret_key": s3_secret_key,
"s3_store_bucket": s3_bucket,
}
},
'enabled_backends': 'local:file, s3:s3',
'default_store_backend': 's3',
})
@patch('charmhelpers.contrib.openstack.context.relation_ids')
@patch('charmhelpers.contrib.hahelpers.cluster.config_get')
@patch('charmhelpers.contrib.openstack.context.https')