# Copyright 2015 Mirantis, Inc. # # 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 logging import os import shutil import tarfile import tempfile import yaml from bareon.utils import utils from fuel_bootstrap import consts from fuel_bootstrap import errors from fuel_bootstrap.objects import master_node_settings from fuel_bootstrap import settings from fuel_bootstrap.utils import data as data_util CONF = settings.Configuration() LOG = logging.getLogger(__name__) ACTIVE = 'active' def get_all(): data = [] LOG.debug("Searching images in %s", CONF.bootstrap_images_dir) for name in os.listdir(CONF.bootstrap_images_dir): if not os.path.isdir(os.path.join(CONF.bootstrap_images_dir, name)): continue try: data.append(parse(name)) except errors.IncorrectImage as e: LOG.debug("Image [%s] is skipped due to %s", name, e) return data def parse(image_uuid): LOG.debug("Trying to parse [%s] image", image_uuid) dir_path = full_path(image_uuid) if os.path.islink(dir_path) or not os.path.isdir(dir_path): raise errors.IncorrectImage("There are no such image [{0}]." .format(image_uuid)) metafile = os.path.join(dir_path, consts.METADATA_FILE) if not os.path.exists(metafile): raise errors.IncorrectImage("Image [{0}] doen's contain metadata file." .format(image_uuid)) with open(metafile) as f: try: data = yaml.safe_load(f) except yaml.YAMLError as e: raise errors.IncorrectImage("Couldn't parse metadata file for" " image [{0}] due to {1}" .format(image_uuid, e)) if data.get('uuid') != os.path.basename(dir_path): raise errors.IncorrectImage("UUID from metadata file [{0}] doesn't" " equal directory name [{1}]" .format(data.get('uuid'), image_uuid)) data['status'] = ACTIVE if is_active(data['uuid']) else '' data.setdefault('label', '') return data def delete(image_uuid): dir_path = full_path(image_uuid) image = parse(image_uuid) if image['status'] == ACTIVE: raise errors.ActiveImageException("Image [{0}] is active and can't be" " deleted.".format(image_uuid)) shutil.rmtree(dir_path) return image_uuid def is_active(image_uuid): return full_path(image_uuid) == os.path.realpath( CONF.active_bootstrap_symlink) def full_path(image_uuid): if not os.path.isabs(image_uuid): return os.path.join(CONF.bootstrap_images_dir, image_uuid) return image_uuid def import_image(arch_path): extract_dir = tempfile.mkdtemp() extract_to_dir(arch_path, extract_dir) metafile = os.path.join(extract_dir, consts.METADATA_FILE) with open(metafile) as f: try: data = yaml.safe_load(f) except yaml.YAMLError as e: raise errors.IncorrectImage("Couldn't parse metadata file" " due to {0}".format(e)) image_uuid = data['uuid'] dir_path = full_path(image_uuid) if os.path.exists(dir_path): raise errors.ImageAlreadyExists("Image [{0}] already exists." .format(image_uuid)) shutil.move(extract_dir, dir_path) os.chmod(dir_path, 0o755) for root, dirs, files in os.walk(dir_path): for d in dirs: os.chmod(os.path.join(root, d), 0o755) for f in files: os.chmod(os.path.join(root, f), 0o755) return image_uuid def extract_to_dir(arch_path, extract_path): LOG.info("Try extract %s to %s", arch_path, extract_path) tarfile.open(arch_path, 'r').extractall(extract_path) def make_bootstrap(data=None): if not data: data = {} bootdata_builder = data_util.BootstrapDataBuilder(data) bootdata = bootdata_builder.build() LOG.info("Try to build image with data:\n%s", yaml.safe_dump(bootdata)) with tempfile.NamedTemporaryFile() as f: f.write(yaml.safe_dump(bootdata)) f.flush() opts = ['fa_mkbootstrap', '--nouse-syslog', '--data_driver', 'bootstrap_build_image', '--nodebug', '-v', '--input_data_file', f.name] if data.get('image_build_dir'): opts.extend(['--image_build_dir', data['image_build_dir']]) utils.execute(*opts) return bootdata['bootstrap']['uuid'], bootdata['output'] def activate(image_uuid=""): is_centos = image_uuid.lower() == 'centos' symlink = CONF.active_bootstrap_symlink if os.path.lexists(symlink): os.unlink(symlink) LOG.debug("Symlink %s was deleted", symlink) if not is_centos: parse(image_uuid) dir_path = full_path(image_uuid) os.symlink(dir_path, symlink) LOG.debug("Symlink %s to %s directory has been created", symlink, dir_path) else: LOG.warning("WARNING: switching to depracated centos-bootstrap") # FIXME: Do normal activation when it become clear how to do it flavor = 'centos' if is_centos else 'ubuntu' utils.execute('fuel-bootstrap-image-set', flavor) return image_uuid def call_wrapped_method(name, notify_webui, **kwargs): wrapped_methods = { 'build': make_bootstrap, 'activate': activate } failed = False try: return wrapped_methods[name](**kwargs) except Exception: failed = True raise finally: if notify_webui: notify_webui_about_results(failed, consts.ERROR_MSG) def notify_webui_about_results(failed, error_message): mn_settings = master_node_settings.MasterNodeSettings() settings = mn_settings.get() settings['settings'].setdefault('bootstrap', {}).setdefault('error', {}) if not failed: error_message = "" settings['settings']['bootstrap']['error']['value'] = error_message mn_settings.update(settings)