1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
|
# 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 = str(ex)
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.ensure_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)
|