trove/trove/guestagent/module/module_manager.py

230 lines
8.9 KiB
Python

# Copyright 2016 Tesora, Inc.
# All Rights Reserved.
#
# 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 datetime
import operator
import os
from oslo_log import log as logging
from trove.common import exception
from trove.common.i18n import _
from trove.common import stream_codecs
from trove.guestagent.common import guestagent_utils
from trove.guestagent.common import operating_system
LOG = logging.getLogger(__name__)
class ModuleManager(object):
"""This is a Manager utility class (mixin) for managing module-related
tasks.
"""
MODULE_APPLY_TO_ALL = 'all'
MODULE_BASE_DIR = guestagent_utils.build_file_path('~', 'modules')
MODULE_CONTENTS_FILENAME = 'contents.dat'
MODULE_RESULT_FILENAME = 'result.json'
@classmethod
def get_current_timestamp(cls):
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[0:22]
@classmethod
def apply_module(cls, driver, module_type, name, tenant,
datastore, ds_version, contents, module_id, md5,
auto_apply, visible, admin_module):
tenant = tenant or cls.MODULE_APPLY_TO_ALL
datastore = datastore or cls.MODULE_APPLY_TO_ALL
ds_version = ds_version or cls.MODULE_APPLY_TO_ALL
module_dir = cls.build_module_dir(module_type, module_id)
data_file = cls.write_module_contents(module_dir, contents, md5)
applied = True
message = None
now = cls.get_current_timestamp()
default_result = cls.build_default_result(
module_type, name, tenant, datastore,
ds_version, module_id, md5,
auto_apply, visible, now, admin_module)
result = cls.read_module_result(module_dir, default_result)
try:
driver.configure(name, datastore, ds_version, data_file)
applied, message = driver.apply(
name, datastore, ds_version, data_file, admin_module)
except Exception as ex:
LOG.exception(_("Could not apply module '%s'"), name)
applied = False
message = ex.message
finally:
status = 'OK' if applied else 'ERROR'
result['removed'] = None
result['status'] = status
result['message'] = message
result['updated'] = now
result['id'] = module_id
result['md5'] = md5
result['type'] = module_type
result['name'] = name
result['datastore'] = datastore
result['datastore_version'] = ds_version
result['tenant'] = tenant
result['auto_apply'] = auto_apply
result['visible'] = visible
result['is_admin'] = admin_module
cls.write_module_result(module_dir, result)
return result
@classmethod
def build_module_dir(cls, module_type, module_id):
sub_dir = os.path.join(module_type, module_id)
module_dir = guestagent_utils.build_file_path(
cls.MODULE_BASE_DIR, sub_dir)
if not operating_system.exists(module_dir, is_directory=True):
operating_system.create_directory(module_dir, force=True)
return module_dir
@classmethod
def write_module_contents(cls, module_dir, contents, md5):
contents_file = cls.build_contents_filename(module_dir)
operating_system.write_file(contents_file, contents,
codec=stream_codecs.Base64Codec(),
encode=False)
return contents_file
@classmethod
def build_contents_filename(cls, module_dir):
contents_file = guestagent_utils.build_file_path(
module_dir, cls.MODULE_CONTENTS_FILENAME)
return contents_file
@classmethod
def build_default_result(cls, module_type, name, tenant,
datastore, ds_version, module_id, md5,
auto_apply, visible, now, admin_module):
result = {
'type': module_type,
'name': name,
'datastore': datastore,
'datastore_version': ds_version,
'tenant': tenant,
'id': module_id,
'md5': md5,
'status': None,
'message': None,
'created': now,
'updated': now,
'removed': None,
'auto_apply': auto_apply,
'visible': visible,
'is_admin': admin_module,
'contents': None,
}
return result
@classmethod
def is_admin_module(cls, tenant, auto_apply, visible):
return (not visible or tenant == cls.MODULE_APPLY_TO_ALL or
auto_apply)
@classmethod
def read_module_result(cls, result_file, default=None):
result_file = cls.get_result_filename(result_file)
result = default
try:
result = operating_system.read_file(
result_file, codec=stream_codecs.JsonCodec())
except Exception:
if not result:
LOG.exception(_("Could not find module result in %s"),
result_file)
raise
return result
@classmethod
def get_result_filename(cls, file_or_dir):
result_file = file_or_dir
if operating_system.exists(file_or_dir, is_directory=True):
result_file = guestagent_utils.build_file_path(
file_or_dir, cls.MODULE_RESULT_FILENAME)
return result_file
@classmethod
def write_module_result(cls, result_file, result):
result_file = cls.get_result_filename(result_file)
operating_system.write_file(
result_file, result, codec=stream_codecs.JsonCodec())
@classmethod
def read_module_results(cls, is_admin=False, include_contents=False):
"""Read all the module results on the guest and return a list
of them.
"""
results = []
pattern = cls.MODULE_RESULT_FILENAME
result_files = operating_system.list_files_in_directory(
cls.MODULE_BASE_DIR, recursive=True, pattern=pattern)
for result_file in result_files:
result = cls.read_module_result(result_file)
if (not result.get('removed') and
(is_admin or result.get('visible'))):
if include_contents:
codec = stream_codecs.Base64Codec()
# keep admin_only for backwards compatibility
if not is_admin and (result.get('is_admin') or
result.get('admin_only')):
contents = (
"Must be admin to retrieve contents for module %s"
% result.get('name', 'Unknown'))
result['contents'] = codec.serialize(contents)
else:
contents_dir = os.path.dirname(result_file)
contents_file = cls.build_contents_filename(
contents_dir)
result['contents'] = operating_system.read_file(
contents_file, codec=codec, decode=False)
results.append(result)
results.sort(key=operator.itemgetter('updated'), reverse=True)
return results
@classmethod
def remove_module(cls, driver, module_type, module_id, name,
datastore, ds_version):
datastore = datastore or cls.MODULE_APPLY_TO_ALL
ds_version = ds_version or cls.MODULE_APPLY_TO_ALL
module_dir = cls.build_module_dir(module_type, module_id)
contents_file = cls.build_contents_filename(module_dir)
if not operating_system.exists(cls.get_result_filename(module_dir)):
raise exception.NotFound(
_("Module '%s' has not been applied") % name)
try:
driver.configure(name, datastore, ds_version, contents_file)
removed, message = driver.remove(
name, datastore, ds_version, contents_file)
cls.remove_module_result(module_dir)
except Exception:
LOG.exception(_("Could not remove module '%s'"), name)
raise
return removed, message
@classmethod
def remove_module_result(cls, result_file):
now = cls.get_current_timestamp()
result = cls.read_module_result(result_file, None)
result['removed'] = now
cls.write_module_result(result_file, result)