Remove local apache2 install, rework use_swift

The functionality of use_swift where a local apache2 instance was set up
for metadata was deprecated for removal and this change actually removes
this. However, the option itself can still be used as an indicator
whether Swift is intended to be used for simplestreams metadata or not.
If the usage is explicitly requested, the charm but Swift endpoints are
not yet present, the charm will now will set the unit state to
maintenance in addition to local logging that was done previously.

Swift presence checking is now endpoint-based instead of being
service-based (no use for the case where endpoints are not set up while
the service is in the catalog).

The lack of swift presence is also ignored for the purposes of
generating proxy setting context - if the endpoint is not there there is
not point in trying to generate NO_PROXY rules for it.

This change also makes test bundles use ceph as a storage medium for
glance since out of space errors were encountered during testing as the
glance unit was running out of space for image storage.

Closes-Bug: #1942047
Closes-Bug: #1934563
Closes-Bug: #1938069

Change-Id: I6519b1449806ad19ee4545501bc4631c9f8e535f
(cherry picked from commit d1f8c4ba4c)
This commit is contained in:
Dmitrii Shcherbakov 2021-08-30 16:17:32 +03:00 committed by Hemanth Nakkina
parent a18cca2ec0
commit 87d8f0b309
15 changed files with 275 additions and 33 deletions

View File

@ -17,12 +17,9 @@ options:
type: boolean
default: True
description: >
DEPRECATED FOR REMOVAL
Should the charm set up the product-streams endpoint with swift's URLs?
If this value is set to False, product-streams metadata will be hosted
on a local Apache server running on the unit and endpoints will be
registered referencing the local unit. This does not support HA
or TLS and is for testing purposes only.
Controls whether swift will be used for image metadata storage
or not. If set to False, image metadata will not be written to
object store while images will still be synced to Glance.
ignore_proxy_for_object_store:
type: boolean
default: true

View File

@ -274,6 +274,7 @@ def do_sync(ksc, charm_conf):
SWIFT_DATA_DIR
]
else:
# For debugging purposes only.
sync_command += [
"--output-dir",
tmpdir
@ -308,14 +309,6 @@ def do_sync(ksc, charm_conf):
log.debug("command: %s", " ".join(sync_command))
log.debug("sstream-mirror environment: %s", sstream_mirror_env)
subprocess.check_call(sync_command, env=sstream_mirror_env)
if not charm_conf['use_swift']:
# Sync output directory to APACHE_DATA_DIR
subprocess.check_call([
'rsync', '-avz',
os.path.join(tmpdir, region_name, 'streams'),
APACHE_DATA_DIR
])
finally:
shutil.rmtree(tmpdir)
@ -354,7 +347,7 @@ def get_sstream_mirror_proxy_env(ksc, region_name,
urlparse.urlparse(u).hostname for u in itertools.chain(
get_service_endpoints(ksc, 'identity', region_name).values(),
get_service_endpoints(ksc, 'image', region_name).values(),
get_service_endpoints(ksc, 'object-store', region_name).values()
get_object_store_endpoints(ksc, region_name)
if ignore_proxy_for_object_store else [],
)])
no_proxy = ','.join(no_proxy_set | additional_hosts)
@ -380,6 +373,26 @@ def update_product_streams_service(ksc, services, region):
)
def get_object_store_endpoints(ksc, region_name):
"""Get object-store endpoints from the service catalog.
The lack of those endpoints is not fatal for the purposes of this script
since a deployment may not have those in which case glance would still
be populated with images but metadata would not have a place to be stored.
:param ksc: An instance of a Keystone client.
:type ksc: :class: `keystoneclient.v3.client.Client`
:param str region_name: A name of the region to retrieve endpoints for.
"""
endpoints = []
try:
endpoints = list(get_service_endpoints(ksc, 'object-store',
region_name).values())
except keystone_exceptions.EndpointNotFound:
log.debug('object-store endpoints are not present')
return endpoints
def get_service_endpoints(ksc, service_type, region_name):
"""Get endpoints for a given service type from the Keystone catalog.
@ -396,6 +409,9 @@ def get_service_endpoints(ksc, service_type, region_name):
region_name=region_name)
for endpoint_type in ['publicURL', 'internalURL', 'adminURL']}
except keystone_exceptions.EndpointNotFound:
# EndpointNotFound is raised for the case where a service does not
# exist as well as for the case where the service exists but not
# endpoints.
log.error('could not retrieve any {} endpoints'.format(service_type))
raise
return catalog
@ -487,6 +503,43 @@ def cleanup():
raise e
def set_active_status(is_object_store_present_and_used):
"""Get object-store endpoints from the service catalog.
The lack of those endpoints is not fatal for the purposes of this script
since a deployment may not have those in which case glance would still
be populated with images but metadata would not have a place to be stored.
"""
ts = time.strftime("%x %X")
# "Unit is ready" is one of approved message prefixes
# Prefix the message with it will help zaza to understand the status.
if is_object_store_present_and_used:
status_set('active', 'Unit is ready (Glance sync completed at {},'
' metadata uploaded to object store)'.format(ts))
else:
status_set('active', 'Unit is ready (Glance sync completed at {},'
' metadata not uploaded - object-store usage disabled)'
''.format(ts))
def assess_object_store_state(object_store_exists, object_store_requested):
"""Decide whether object store"""
if object_store_requested and not object_store_exists:
# If use_swift is set, we need to wait for swift to become
# available.
msg = ('Swift usage has been requested but'
' its endpoints are not yet in the catalog')
status_set('maintenance', msg)
log.info(msg)
return False
return True
def is_object_store_present(ksc, region_name):
"""Checks whether object store is present (service & endpoints)."""
return len(get_object_store_endpoints(ksc, region_name)) > 0
def main():
log.info("glance-simplestreams-sync started.")
@ -507,27 +560,28 @@ def main():
set_openstack_env(id_conf, charm_conf)
region_name = charm_conf['region']
ksc = get_keystone_client(id_conf['api_version'])
services = [s._info for s in ksc.services.list()]
servicenames = [s['name'] for s in services]
ps_service_exists = PRODUCT_STREAMS_SERVICE_NAME in servicenames
swift_exists = 'swift' in servicenames
object_store_present = is_object_store_present(ksc, region_name)
use_swift = charm_conf['use_swift']
log.info("ps_service_exists={}, charm_conf['use_swift']={}"
", swift_exists={}".format(ps_service_exists,
charm_conf['use_swift'],
swift_exists))
", object_store_present={}".format(ps_service_exists,
use_swift,
object_store_present))
try:
if not swift_exists and charm_conf['use_swift']:
# If use_swift is set, we need to wait for swift to become
# available.
log.info("Swift not yet ready.")
if not assess_object_store_state(object_store_present, use_swift):
return
if ps_service_exists and charm_conf['use_swift'] and swift_exists:
is_object_store_present_and_used = use_swift and object_store_present
if ps_service_exists and is_object_store_present_and_used:
log.info("Updating product streams service.")
update_product_streams_service(ksc, services, charm_conf['region'])
update_product_streams_service(ksc, services, region_name)
else:
log.info("Not updating product streams service.")
@ -535,10 +589,7 @@ def main():
status_set('maintenance', 'Synchronising images')
do_sync(ksc, charm_conf)
ts = time.strftime("%x %X")
# "Unit is ready" is one of approved message prefixes
# Prefix the message with it will help zaza to understand the status.
status_set('active', "Unit is ready (Sync completed at {})".format(ts))
set_active_status(is_object_store_present_and_used)
# If this is an initial per-minute sync attempt, delete it on success.
if os.path.exists(CRON_POLL_FILENAME):

View File

@ -297,8 +297,8 @@ def install():
_packages = PACKAGES
if not hookenv.config("use_swift"):
hookenv.log('Configuring for local hosting of product stream.')
_packages += ["apache2"]
hookenv.log('Swift usage was disabled explicitly: not setting up'
' streams metadata')
if CompareHostReleases(lsb_release()['DISTRIB_CODENAME']) >= 'disco':
_packages = [pkg for pkg in _packages if not pkg.startswith('python-')]

View File

@ -24,6 +24,7 @@ relations:
- ['vault:shared-db', 'mysql:shared-db']
- ['keystone:shared-db', 'mysql:shared-db']
- ['glance:shared-db', 'mysql:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['keystone:certificates', 'vault:certificates']
- ['glance:certificates', 'vault:certificates']
- ['glance-simplestreams-sync:certificates', 'vault:certificates']

View File

@ -27,6 +27,7 @@ relations:
- ['vault:shared-db', 'mysql:shared-db']
- ['keystone:shared-db', 'mysql:shared-db']
- ['glance:shared-db', 'mysql:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['keystone:certificates', 'vault:certificates']
- ['glance:certificates', 'vault:certificates']
- ['glance-simplestreams-sync:certificates', 'vault:certificates']

View File

@ -27,6 +27,7 @@ relations:
- ['vault:shared-db', 'mysql:shared-db']
- ['keystone:shared-db', 'mysql:shared-db']
- ['glance:shared-db', 'mysql:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['keystone:certificates', 'vault:certificates']
- ['glance:certificates', 'vault:certificates']
- ['glance-simplestreams-sync:certificates', 'vault:certificates']

View File

@ -27,6 +27,7 @@ relations:
- ['vault:shared-db', 'mysql:shared-db']
- ['keystone:shared-db', 'mysql:shared-db']
- ['glance:shared-db', 'mysql:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['keystone:certificates', 'vault:certificates']
- ['glance:certificates', 'vault:certificates']
- ['glance-simplestreams-sync:certificates', 'vault:certificates']

View File

@ -27,6 +27,7 @@ relations:
- ['vault:shared-db', 'mysql:shared-db']
- ['keystone:shared-db', 'mysql:shared-db']
- ['glance:shared-db', 'mysql:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['keystone:certificates', 'vault:certificates']
- ['glance:certificates', 'vault:certificates']
- ['glance-simplestreams-sync:certificates', 'vault:certificates']

View File

@ -111,6 +111,7 @@ relations:
- ['glance-simplestreams-sync:identity-service', 'keystone:identity-service']
- ['keystone:shared-db','keystone-mysql-router:shared-db']
- ['glance:shared-db','glance-mysql-router:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['vault:shared-db','vault-mysql-router:shared-db']
- ['keystone-mysql-router:db-router','mysql-innodb-cluster:db-router']
- ['glance-mysql-router:db-router','mysql-innodb-cluster:db-router']

View File

@ -111,6 +111,7 @@ relations:
- ['glance-simplestreams-sync:identity-service', 'keystone:identity-service']
- ['keystone:shared-db','keystone-mysql-router:shared-db']
- ['glance:shared-db','glance-mysql-router:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['vault:shared-db','vault-mysql-router:shared-db']
- ['keystone-mysql-router:db-router','mysql-innodb-cluster:db-router']
- ['glance-mysql-router:db-router','mysql-innodb-cluster:db-router']

View File

@ -111,6 +111,7 @@ relations:
- ['glance-simplestreams-sync:identity-service', 'keystone:identity-service']
- ['keystone:shared-db','keystone-mysql-router:shared-db']
- ['glance:shared-db','glance-mysql-router:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['vault:shared-db','vault-mysql-router:shared-db']
- ['keystone-mysql-router:db-router','mysql-innodb-cluster:db-router']
- ['glance-mysql-router:db-router','mysql-innodb-cluster:db-router']

View File

@ -27,6 +27,7 @@ relations:
- ['vault:shared-db', 'mysql:shared-db']
- ['keystone:shared-db', 'mysql:shared-db']
- ['glance:shared-db', 'mysql:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['keystone:certificates', 'vault:certificates']
- ['glance:certificates', 'vault:certificates']
- ['glance-simplestreams-sync:certificates', 'vault:certificates']

View File

@ -27,6 +27,7 @@ relations:
- ['vault:shared-db', 'mysql:shared-db']
- ['keystone:shared-db', 'mysql:shared-db']
- ['glance:shared-db', 'mysql:shared-db']
- ['glance:ceph', 'ceph-mon:client']
- ['keystone:certificates', 'vault:certificates']
- ['glance:certificates', 'vault:certificates']
- ['glance-simplestreams-sync:certificates', 'vault:certificates']

View File

@ -58,6 +58,11 @@ basepython = python3.8
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
[testenv:py39]
basepython = python3.9
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
[testenv:py3]
basepython = python3
deps = -r{toxinidir}/requirements.txt

View File

@ -174,6 +174,63 @@ JUJU_CONTEXT_ID=glance-simplestreams-sync/0-run-commands-3325280900519425661
self.assertEqual(set(proxy_env['no_proxy'].split(',')),
no_proxy_set_no_obj)
@mock.patch('files.glance_simplestreams_sync.get_service_endpoints')
@mock.patch('files.glance_simplestreams_sync.juju_proxy_settings')
def test_get_sstream_mirror_proxy_env_no_object_store(
self, juju_proxy_settings, get_service_endpoints):
# Use a side effect instead of return value to avoid modification of
# the same dict in different invocations of the tested function.
def juju_proxy_settings_side_effect():
return {
"HTTP_PROXY": "http://squid.internal:3128",
"HTTPS_PROXY": "https://squid.internal:3128",
"NO_PROXY": "127.0.0.1,localhost,::1",
"http_proxy": "http://squid.internal:3128",
"https_proxy": "https://squid.internal:3128",
"no_proxy": "127.0.0.1,localhost,::1",
}
juju_proxy_settings.side_effect = juju_proxy_settings_side_effect
def get_service_endpoints_side_effect(ksc, service_type, region_name):
if service_type == 'object-store':
raise keystone_exceptions.EndpointNotFound('foo')
return {
'identity': {
'publicURL': 'https://192.0.2.42:5000/v3',
'internalURL': 'https://192.0.2.43:5000/v3',
'adminURL': 'https://192.0.2.44:35357/v3',
},
'image': {
'publicURL': 'https://192.0.2.45:9292',
'internalURL': 'https://192.0.2.45:9292',
'adminURL': 'https://192.0.2.47:9292',
},
}[service_type]
get_service_endpoints.side_effect = get_service_endpoints_side_effect
for proxy_env in [
gss.get_sstream_mirror_proxy_env(
mock.MagicMock(), 'TestRegion'),
gss.get_sstream_mirror_proxy_env(
mock.MagicMock(), 'TestRegion',
ignore_proxy_for_object_store=True)]:
self.assertEqual(proxy_env['HTTP_PROXY'],
'http://squid.internal:3128')
self.assertEqual(proxy_env['http_proxy'],
'http://squid.internal:3128')
self.assertEqual(proxy_env['HTTPS_PROXY'],
'https://squid.internal:3128')
self.assertEqual(proxy_env['https_proxy'],
'https://squid.internal:3128')
no_proxy_set = set(['127.0.0.1', 'localhost', '::1', '192.0.2.42',
'192.0.2.43', '192.0.2.44', '192.0.2.45',
'192.0.2.47'])
self.assertEqual(set(proxy_env['NO_PROXY'].split(',')),
no_proxy_set)
self.assertEqual(set(proxy_env['no_proxy'].split(',')),
no_proxy_set)
def test_get_service_endpoints(self):
def url_for_side_effect(service_type, endpoint_type, region_name):
@ -222,7 +279,129 @@ JUJU_CONTEXT_ID=glance-simplestreams-sync/0-run-commands-3325280900519425661
)
ksc.service_catalog.url_for.side_effect = mock.MagicMock(
side_effect=keystone_exceptions.EndpointException('foo'))
side_effect=keystone_exceptions.EndpointNotFound('foo'))
with self.assertRaises(keystone_exceptions.EndpointException):
with self.assertRaises(keystone_exceptions.EndpointNotFound):
gss.get_service_endpoints(ksc, 'test', 'TestRegion')
def test_get_object_store_endpoints(self):
def url_for_side_effect(service_type, endpoint_type, region_name):
return {
'TestRegion': {
'identity': {
'publicURL': 'https://10.5.2.42:443/swift/v1',
'internalURL': 'https://10.5.2.42:443/swift/v1',
'adminURL': 'https://10.5.2.42:443/swift/v1',
},
'image': {
'publicURL': 'https://10.5.2.43:443/swift/v1',
'internalURL': 'https://10.5.2.43:443/swift/v1',
'adminURL': 'https://10.5.2.43:443/swift/v1',
},
'object-store': {
'publicURL': 'https://10.5.2.44:443/swift/v1',
'internalURL': 'https://10.5.2.44:443/swift/v1',
'adminURL': 'https://10.5.2.44:443/swift/v1',
},
}
}[region_name][service_type][endpoint_type]
ksc = mock.MagicMock()
ksc.service_catalog.url_for.side_effect = url_for_side_effect
self.assertEqual(
gss.get_object_store_endpoints(ksc, 'TestRegion'),
['https://10.5.2.44:443/swift/v1',
'https://10.5.2.44:443/swift/v1',
'https://10.5.2.44:443/swift/v1']
)
ksc.service_catalog.url_for.side_effect = mock.MagicMock(
side_effect=keystone_exceptions.EndpointNotFound('foo'))
self.assertEqual(gss.get_object_store_endpoints(ksc, 'TestRegion'), [])
@mock.patch('time.strftime')
@mock.patch('files.glance_simplestreams_sync.status_set')
def test_set_active_status(self, _status_set, _strftime):
_status_set.return_value = None
_strftime.return_value = '42'
gss.set_active_status(is_object_store_present_and_used=True)
_status_set.assert_called_once_with(
'active',
'Unit is ready (Glance sync completed at 42,'
' metadata uploaded to object store)'
)
_status_set.reset_mock()
gss.set_active_status(is_object_store_present_and_used=False)
_status_set.assert_called_once_with(
'active',
'Unit is ready (Glance sync completed at 42,'
' metadata not uploaded - object-store usage disabled)'
)
@mock.patch('files.glance_simplestreams_sync.status_set')
def test_assess_object_store_state(self, _status_set):
self.assertTrue(
gss.assess_object_store_state(object_store_exists=True,
object_store_requested=False))
_status_set.assert_not_called()
_status_set.reset_mock()
self.assertTrue(
gss.assess_object_store_state(object_store_exists=True,
object_store_requested=True))
_status_set.assert_not_called()
_status_set.reset_mock()
self.assertTrue(
gss.assess_object_store_state(object_store_exists=False,
object_store_requested=False))
_status_set.assert_not_called()
_status_set.reset_mock()
self.assertFalse(
gss.assess_object_store_state(object_store_exists=False,
object_store_requested=True))
_status_set.assert_called_once_with(
'maintenance',
'Swift usage has been requested but its endpoints are not yet in'
' the catalog'
)
_status_set.reset_mock()
def test_is_object_store_present(self):
def url_for_side_effect(service_type, endpoint_type, region_name):
return {
'TestRegion': {
'identity': {
'publicURL': 'https://10.5.2.42:443/swift/v1',
'internalURL': 'https://10.5.2.42:443/swift/v1',
'adminURL': 'https://10.5.2.42:443/swift/v1',
},
'image': {
'publicURL': 'https://10.5.2.43:443/swift/v1',
'internalURL': 'https://10.5.2.43:443/swift/v1',
'adminURL': 'https://10.5.2.43:443/swift/v1',
},
'object-store': {
'publicURL': 'https://10.5.2.44:443/swift/v1',
'internalURL': 'https://10.5.2.44:443/swift/v1',
'adminURL': 'https://10.5.2.44:443/swift/v1',
},
}
}[region_name][service_type][endpoint_type]
ksc = mock.MagicMock()
ksc.service_catalog.url_for.side_effect = url_for_side_effect
self.assertTrue(gss.is_object_store_present(ksc, 'TestRegion'))
# Endpoints not found or service not present at all.
ksc.service_catalog.url_for.side_effect = mock.MagicMock(
side_effect=keystone_exceptions.EndpointNotFound('foo'))
self.assertFalse(gss.is_object_store_present(ksc, 'TestRegion'))