267 lines
8.3 KiB
Python
267 lines
8.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2014 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 abc
|
|
import logging
|
|
|
|
import requests
|
|
import six
|
|
|
|
from fuel_upgrade import errors
|
|
from fuel_upgrade import utils
|
|
|
|
from fuel_upgrade.clients import NailgunClient
|
|
from fuel_upgrade.clients import OSTFClient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
class BaseBeforeUpgradeChecker(object):
|
|
"""Base class for before ugprade checkers
|
|
"""
|
|
|
|
@abc.abstractmethod
|
|
def check(self):
|
|
"""Run check
|
|
"""
|
|
|
|
|
|
class CheckNoRunningTasks(BaseBeforeUpgradeChecker):
|
|
"""Checks that there is no running tasks
|
|
|
|
:param config: config object where property endpoints
|
|
returns dict with nailgun host and port
|
|
"""
|
|
|
|
def __init__(self, context):
|
|
nailgun = context.config.endpoints['nginx_nailgun']
|
|
self.nailgun_client = NailgunClient(**nailgun)
|
|
|
|
def check(self):
|
|
"""Sends request to nailgun
|
|
to make sure that there are no
|
|
running tasks
|
|
"""
|
|
logger.info('Check nailgun tasks')
|
|
|
|
try:
|
|
tasks = self.nailgun_client.get_tasks()
|
|
except requests.ConnectionError:
|
|
raise errors.NailgunIsNotRunningError(
|
|
'Cannot connect to rest api service')
|
|
|
|
logger.debug('Nailgun tasks %s', tasks)
|
|
|
|
running_tasks = filter(
|
|
lambda t: t['status'] == 'running', tasks)
|
|
|
|
if running_tasks:
|
|
tasks_msg = ['id={0} cluster={1} name={2}'.format(
|
|
t.get('id'),
|
|
t.get('cluster'),
|
|
t.get('name')) for t in running_tasks]
|
|
|
|
error_msg = 'Cannot run upgrade, tasks are running: {0}'.format(
|
|
' '.join(tasks_msg))
|
|
|
|
raise errors.CannotRunUpgrade(error_msg)
|
|
|
|
|
|
class CheckNoRunningOstf(BaseBeforeUpgradeChecker):
|
|
"""Checks that there's no running OSTF tasks.
|
|
|
|
:param context: a context object with config and required space info
|
|
"""
|
|
|
|
def __init__(self, context):
|
|
self.ostf = OSTFClient(**context.config.endpoints['ostf'])
|
|
|
|
def check(self):
|
|
logger.info('Check OSTF tasks')
|
|
|
|
try:
|
|
tasks = self.ostf.get_tasks()
|
|
except requests.ConnectionError:
|
|
raise errors.OstfIsNotRunningError(
|
|
'Cannot connect to OSTF service.')
|
|
|
|
logger.debug('OSTF tasks: %s', tasks)
|
|
|
|
running_tasks = filter(
|
|
lambda t: t['status'] == 'running', tasks)
|
|
|
|
if running_tasks:
|
|
raise errors.CannotRunUpgrade(
|
|
'Cannot run upgrade since there are OSTF running tasks.')
|
|
|
|
|
|
class CheckFreeSpace(BaseBeforeUpgradeChecker):
|
|
"""Checks that there is enough free space on devices
|
|
|
|
:param list upgraders: list of upgarde engines
|
|
"""
|
|
|
|
def __init__(self, context):
|
|
self.required_spaces = context.required_free_spaces
|
|
|
|
def check(self):
|
|
"""Check free space
|
|
"""
|
|
logger.info('Check if devices have enough free space')
|
|
logger.debug(
|
|
'Required spaces from upgrade '
|
|
'engines %s', self.required_spaces)
|
|
|
|
mount_points = self.space_required_for_mount_points()
|
|
logger.debug(
|
|
'Mount points and sum of required spaces '
|
|
'%s', mount_points)
|
|
|
|
error_mount_point = self.list_of_error_mount_points(mount_points)
|
|
logger.debug(
|
|
"Mount points which don't have "
|
|
"enough free space %s", error_mount_point)
|
|
|
|
self.check_result(error_mount_point)
|
|
|
|
def space_required_for_mount_points(self):
|
|
"""Iterates over required spaces generates
|
|
list of mount points with sum of required space
|
|
|
|
:returns: dict where key is mount point
|
|
and value is required free space
|
|
"""
|
|
sum_of_spaces = {}
|
|
for required_space in self.required_spaces:
|
|
if not required_space:
|
|
continue
|
|
|
|
for path, size in sorted(required_space.items()):
|
|
mount_path = utils.find_mount_point(path)
|
|
sum_of_spaces.setdefault(mount_path, 0)
|
|
sum_of_spaces[mount_path] += size
|
|
|
|
return sum_of_spaces
|
|
|
|
def list_of_error_mount_points(self, mount_points):
|
|
"""Returns list of devices which don't have
|
|
enough free space
|
|
|
|
:param list mount_points: elements are dicts
|
|
where key is path to mount point
|
|
and value is required space for
|
|
this mount point
|
|
:returns: list where elements are dicts
|
|
{'path': 'path to mount point',
|
|
'size': 'required free space'
|
|
'available': 'available free space'}
|
|
"""
|
|
free_space_error_devices = []
|
|
for path, required_size in sorted(mount_points.items()):
|
|
free_space = utils.calculate_free_space(path)
|
|
|
|
if free_space < required_size:
|
|
free_space_error_devices.append({
|
|
'path': path,
|
|
'size': required_size,
|
|
'available': free_space})
|
|
|
|
return free_space_error_devices
|
|
|
|
def check_result(self, error_devices):
|
|
"""Checks if there are some devices which
|
|
don't have enough free space for upgrades
|
|
|
|
:raises: NotEnoughFreeSpaceOnDeviceError
|
|
"""
|
|
if not error_devices:
|
|
return
|
|
|
|
devices_msg = [
|
|
'device {0} ('
|
|
'required {1}MB, '
|
|
'available {2}MB, '
|
|
'not enough {3}MB'
|
|
')'.format(
|
|
d['path'],
|
|
d['size'],
|
|
d['available'],
|
|
d['size'] - d['available']) for d in error_devices]
|
|
|
|
err_msg = 'Not enough free space on device: {0}'.format(
|
|
', '.join(devices_msg))
|
|
|
|
raise errors.NotEnoughFreeSpaceOnDeviceError(err_msg)
|
|
|
|
|
|
class CheckUpgradeVersions(BaseBeforeUpgradeChecker):
|
|
"""Checks that it is possible to upgarde from
|
|
current version to new one.
|
|
|
|
:param config: config object
|
|
"""
|
|
|
|
def __init__(self, context):
|
|
config = context.config
|
|
#: version of fuel which user wants to upgrade from
|
|
self.from_version = config.from_version
|
|
#: version of fuel which user wants to upgrade to
|
|
self.to_version = config.new_version
|
|
|
|
def check(self):
|
|
"""Compares two versions previous and new
|
|
|
|
:raises: WrongVersionError
|
|
"""
|
|
logger.info('Check upgrade versions')
|
|
|
|
result = utils.compare_version(self.from_version, self.to_version)
|
|
err_msg = None
|
|
if result == 0:
|
|
err_msg = 'Cannot upgrade to the same version of fuel ' \
|
|
'{0} -> {1}'.format(
|
|
self.from_version, self.to_version)
|
|
elif result == -1:
|
|
err_msg = 'Cannot upgrade from higher version of fuel ' \
|
|
'to lower {0} -> {1}'.format(
|
|
self.from_version, self.to_version)
|
|
|
|
if err_msg:
|
|
raise errors.WrongVersionError(err_msg)
|
|
|
|
|
|
class CheckRequiredVersion(BaseBeforeUpgradeChecker):
|
|
"""Checks that user's going to upgrade Fuel from the required version.
|
|
|
|
:param context: a context object with config and required space info
|
|
"""
|
|
|
|
def __init__(self, context):
|
|
#: version of fuel which user wants to upgrade from
|
|
self.from_version = context.config.from_version
|
|
#: a list of versions from which user can upgrade
|
|
self.can_upgrade_from = context.config.can_upgrade_from
|
|
|
|
def check(self):
|
|
logger.info('Check required Fuel version')
|
|
|
|
if self.from_version not in self.can_upgrade_from:
|
|
raise errors.WrongVersionError(
|
|
'Cannot upgrade from Fuel {0}. You can upgrade only from '
|
|
'one of next versions: {1}'.format(
|
|
self.from_version, ', '.join(self.can_upgrade_from)))
|