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:
parent
b1cae3160a
commit
a6dc972b09
23
README.md
23
README.md
|
@ -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).
|
||||
|
|
26
config.yaml
26
config.yaml
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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", ""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue