
1. Refactor dcorch's generic_sync_manager.py and initial_sync_manager into a main process manager and a worker manager. The main manager will handle the allocation of eligible subclouds to each worker. 2. Rename the current EngineService to EngineWorkerService and introduce a new EngineService for the main process, similar to DCManagerAuditService and DCManagerAuditWorkerService. 3. Rename the current RPC EngineClient to EngineWorkerClient and introduce a new EngineClient. Adapt the RPC methods to accommodate the modifications in these main process managers and worker managers. 4. Move master resources data retrieval from each sync_thread to engine workers. 5. Implement 2 new db APIs for subcloud batch sync and state updates. 6. Remove code related to sync_lock and its associated db table schema. 7. Add ocf script for managing the start and stop of the dcorch engine-worker service, and make changes in packaging accordingly. 8. Bug fixes for the issues related to the usage of base64.urlsafe_b64encode and base64.urlsafe_b64decode in python3. 9. Update unit tests for the main process and worker managers. Test Plan: PASS: Verify that the dcorch audit runs properly every 5 minutes. PASS: Verify that the initial sync runs properly every 10 seconds. PASS: Verify that the sync subclouds operation runs properly every 5 seconds. PASS: Successfully start and stop the dcorch-engine and dcorch-engine-worker services using the sm commands. PASS: Change the admin password on the system controller using the command "openstack --os-region-name SystemController user password set". Verify that the admin password is synchronized to the subcloud and the dcorch receives the corresponding sync request, followed by successful execution of sync resources for the subcloud. PASS: Unmanage and then manage a subcloud, and verify that the initial sync is executed successfully for that subcloud. PASS: Verify the removal of the sync_lock table from the dcorch db. Story: 2011106 Task: 50013 Change-Id: I329847bd1107ec43e67ec59bdd1e3111b7b37cd3 Signed-off-by: lzhu1 <li.zhu@windriver.com>
250 lines
8.5 KiB
Python
250 lines
8.5 KiB
Python
# Copyright 2017-2024 Wind River
|
|
#
|
|
# 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.
|
|
|
|
import base64
|
|
from cryptography import fernet
|
|
import msgpack
|
|
import psutil
|
|
import six
|
|
from six.moves.urllib.parse import urlparse
|
|
|
|
from keystoneauth1 import exceptions as keystone_exceptions
|
|
from oslo_log import log as logging
|
|
|
|
from dccommon import consts as dccommon_consts
|
|
from dccommon.drivers.openstack import sdk_platform as sdk
|
|
from dcorch.common import consts
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def is_space_available(partition, size):
|
|
available_space = psutil.disk_usage(partition).free
|
|
return False if available_space < size else True
|
|
|
|
|
|
def get_host_port_options(cfg):
|
|
if cfg.type == consts.ENDPOINT_TYPE_COMPUTE:
|
|
return cfg.compute.bind_host, cfg.compute.bind_port
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_PLATFORM:
|
|
return cfg.platform.bind_host, cfg.platform.bind_port
|
|
elif cfg.type == consts.ENDPOINT_TYPE_NETWORK:
|
|
return cfg.network.bind_host, cfg.network.bind_port
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_SOFTWARE:
|
|
return cfg.usm.bind_host, cfg.usm.bind_port
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_PATCHING:
|
|
return cfg.patching.bind_host, cfg.patching.bind_port
|
|
elif cfg.type == consts.ENDPOINT_TYPE_VOLUME:
|
|
return cfg.volume.bind_host, cfg.volume.bind_port
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_IDENTITY:
|
|
return cfg.identity.bind_host, cfg.identity.bind_port
|
|
else:
|
|
LOG.error("Type: %s is undefined! Ignoring", cfg.type)
|
|
return None, None
|
|
|
|
|
|
def get_remote_host_port_options(cfg):
|
|
if cfg.type == consts.ENDPOINT_TYPE_COMPUTE:
|
|
return cfg.compute.remote_host, cfg.compute.remote_port
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_PLATFORM:
|
|
return cfg.platform.remote_host, cfg.platform.remote_port
|
|
elif cfg.type == consts.ENDPOINT_TYPE_NETWORK:
|
|
return cfg.network.remote_host, cfg.network.remote_port
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_SOFTWARE:
|
|
return cfg.usm.remote_host, cfg.usm.remote_port
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_PATCHING:
|
|
return cfg.patching.remote_host, cfg.patching.remote_port
|
|
elif cfg.type == consts.ENDPOINT_TYPE_VOLUME:
|
|
return cfg.volume.remote_host, cfg.volume.remote_port
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_IDENTITY:
|
|
return cfg.identity.remote_host, cfg.identity.remote_port
|
|
else:
|
|
LOG.error("Type: %s is undefined! Ignoring", cfg.type)
|
|
return None, None
|
|
|
|
|
|
def get_sync_endpoint(cfg):
|
|
if cfg.type == consts.ENDPOINT_TYPE_COMPUTE:
|
|
return cfg.compute.sync_endpoint
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_PLATFORM:
|
|
return cfg.platform.sync_endpoint
|
|
elif cfg.type == consts.ENDPOINT_TYPE_NETWORK:
|
|
return cfg.network.sync_endpoint
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_PATCHING:
|
|
return cfg.patching.sync_endpoint
|
|
elif cfg.type == consts.ENDPOINT_TYPE_VOLUME:
|
|
return cfg.volume.sync_endpoint
|
|
elif cfg.type == dccommon_consts.ENDPOINT_TYPE_IDENTITY:
|
|
return cfg.identity.sync_endpoint
|
|
else:
|
|
LOG.error("Type: %s is undefined! Ignoring", cfg.type)
|
|
return None
|
|
|
|
|
|
def get_url_path_components(url):
|
|
result = urlparse(url)
|
|
return result.path.split('/')
|
|
|
|
|
|
def get_routing_match_arguments(environ):
|
|
return environ['wsgiorg.routing_args'][1]
|
|
|
|
|
|
def get_routing_match_value(environ, key):
|
|
match = get_routing_match_arguments(environ)
|
|
if key in match:
|
|
return match[key]
|
|
else:
|
|
LOG.info("(%s) is not available in routing match arguments.", key)
|
|
for k, v in match.items():
|
|
LOG.info("Match key:(%s), value:(%s)", k, v)
|
|
return None
|
|
|
|
|
|
def get_operation_type(environ):
|
|
return environ['REQUEST_METHOD'].lower()
|
|
|
|
|
|
def get_id_from_query_string(environ, id):
|
|
import six.moves.urllib.parse as six_urlparse
|
|
params = six_urlparse.parse_qs(environ.get('QUERY_STRING', ''))
|
|
return params.get(id, [None])[0]
|
|
|
|
|
|
def get_user_id(environ):
|
|
return get_id_from_query_string(environ, 'user_id')
|
|
|
|
|
|
def show_usage(environ):
|
|
return get_id_from_query_string(environ, 'usage') == 'True'
|
|
|
|
|
|
def get_tenant_id(environ):
|
|
return get_routing_match_value(environ, 'tenant_id')
|
|
|
|
|
|
def set_request_forward_environ(req, remote_host, remote_port):
|
|
req.environ['HTTP_X_FORWARDED_SERVER'] = req.environ.get(
|
|
'HTTP_HOST', '')
|
|
req.environ['HTTP_X_FORWARDED_SCHEME'] = req.environ['wsgi.url_scheme']
|
|
req.environ['HTTP_HOST'] = remote_host + ':' + str(remote_port)
|
|
req.environ['SERVER_NAME'] = remote_host
|
|
req.environ['SERVER_PORT'] = remote_port
|
|
if ('REMOTE_ADDR' in req.environ and 'HTTP_X_FORWARDED_FOR' not in
|
|
req.environ):
|
|
req.environ['HTTP_X_FORWARDED_FOR'] = req.environ['REMOTE_ADDR']
|
|
|
|
|
|
def _get_fernet_keys():
|
|
"""Get fernet keys from sysinv."""
|
|
os_client = sdk.OpenStackDriver(region_name=dccommon_consts.CLOUD_0,
|
|
thread_name='proxy')
|
|
try:
|
|
key_list = os_client.sysinv_client.get_fernet_keys()
|
|
return [str(getattr(key, 'key')) for key in key_list]
|
|
except (keystone_exceptions.connection.ConnectTimeout,
|
|
keystone_exceptions.ConnectFailure) as e:
|
|
LOG.info("get_fernet_keys: cloud {} is not reachable [{}]"
|
|
.format(dccommon_consts.CLOUD_0, str(e)))
|
|
sdk.OpenStackDriver.delete_region_clients(dccommon_consts.CLOUD_0)
|
|
return None
|
|
except (AttributeError, TypeError) as e:
|
|
LOG.info("get_fernet_keys error {}".format(e))
|
|
sdk.OpenStackDriver.delete_region_clients(dccommon_consts.CLOUD_0,
|
|
clear_token=True)
|
|
return None
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
return None
|
|
|
|
|
|
def _restore_padding(token):
|
|
"""Restore padding based on token size.
|
|
|
|
:param token: token to restore padding on
|
|
:returns: token with correct padding
|
|
"""
|
|
|
|
# Re-inflate the padding
|
|
mod_returned = len(token) % 4
|
|
if mod_returned:
|
|
missing_padding = 4 - mod_returned
|
|
token += b'=' * missing_padding
|
|
return token
|
|
|
|
|
|
def _unpack_token(fernet_token, fernet_keys):
|
|
"""Attempt to unpack a token using the supplied Fernet keys.
|
|
|
|
:param fernet_token: token to unpack
|
|
:type fernet_token: string
|
|
:param fernet_keys: a list consisting of keys in the repository
|
|
:type fernet_keys: list
|
|
:returns: the token payload
|
|
"""
|
|
|
|
# create a list of fernet instances
|
|
fernet_instances = [fernet.Fernet(key) for key in fernet_keys]
|
|
# create a encryption/decryption object from the fernet keys
|
|
crypt = fernet.MultiFernet(fernet_instances)
|
|
|
|
# attempt to decode the token
|
|
token = _restore_padding(six.binary_type(fernet_token))
|
|
serialized_payload = crypt.decrypt(token)
|
|
payload = msgpack.unpackb(serialized_payload)
|
|
|
|
# present token values
|
|
return payload
|
|
|
|
|
|
def retrieve_token_audit_id(fernet_token):
|
|
"""Attempt to retrieve the audit id from the fernet token.
|
|
|
|
:param fernet_token:
|
|
:param keys_repository:
|
|
:return: audit id in base64 encoded (without paddings)
|
|
"""
|
|
|
|
audit_id = None
|
|
fernet_keys = _get_fernet_keys()
|
|
LOG.info("fernet_keys: {}".format(fernet_keys))
|
|
|
|
if fernet_keys:
|
|
unpacked_token = _unpack_token(fernet_token, fernet_keys)
|
|
if unpacked_token:
|
|
audit_id = unpacked_token[-1][0]
|
|
audit_id = base64.urlsafe_b64encode(
|
|
audit_id.encode('utf-8')).rstrip(b'=').decode('utf-8')
|
|
|
|
return audit_id
|
|
|
|
|
|
def cleanup(environ):
|
|
"""Close any temp files that might have opened.
|
|
|
|
:param environ: a request environment
|
|
:return: None
|
|
"""
|
|
|
|
if 'webob._parsed_post_vars' in environ:
|
|
post_vars, body_file = environ['webob._parsed_post_vars']
|
|
# the content is copied into a BytesIO or temporary file
|
|
if not isinstance(body_file, bytes):
|
|
body_file.close()
|
|
for f in post_vars.keys():
|
|
item = post_vars[f]
|
|
if hasattr(item, 'file'):
|
|
item.file.close()
|