fuel-devops/devops/shell.py
Vladimir Khlyunev 2de2a92e7e Remove debug output from patch 500747
https://review.openstack.org/#/c/500747/ contains one debug line which
should not exists

Change-Id: Iae2d130216dbfb6210df1ed6337eabc0b2752391
2017-09-05 16:41:40 +04:00

760 lines
33 KiB
Python

# Copyright 2013 - 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.
from __future__ import print_function
import argparse
import collections
import os
import sys
import datetime
from netaddr import IPNetwork
# pylint: disable=redefined-builtin
from six.moves import input
from six.moves import xrange
# pylint: enable=redefined-builtin
import tabulate
import devops
from devops.helpers.helpers import get_file_size
from devops.helpers import node_manager
from devops.helpers.ntp import sync_time
from devops.helpers.templates import create_admin_config
from devops.models import Environment
from devops.models.network import Network
from devops import settings
class Shell(object):
def __init__(self, args):
self.args = args
self.params = self.get_params()
if getattr(self.params, 'snapshot-name', None):
self.snapshot_name = getattr(self.params, 'snapshot-name')
if (getattr(self.params, 'name', None) and
getattr(self.params, 'command', None) != 'create'):
try:
self.env = Environment.get(name=self.params.name)
except Environment.DoesNotExist:
self.env = None
sys.exit("Environment with name {} doesn't exist."
"".format(self.params.name))
def execute(self):
self.commands.get(self.params.command)(self)
def print_table(self, headers, columns):
print(tabulate.tabulate(columns, headers=headers,
tablefmt="simple"))
@staticmethod
def query_yes_no(question, default=None):
"""Ask a yes/no question via standard input and return the answer.
If invalid input is given, the user will be asked until
they acutally give valid input.
Args:
question(str):
A question that is presented to the user.
default(bool|None):
The default value when enter is pressed with no value.
When None, there is no default value and the query
will loop.
Returns:
A bool indicating whether user has entered yes or no.
Side Effects:
Blocks program execution until valid input(y/n) is given.
"""
yes_list = ["yes", "y"]
no_list = ["no", "n"]
default_dict = { # default => prompt default string
None: "[y/n]",
True: "[Y/n]",
False: "[y/N]",
}
default_str = default_dict[default]
prompt_str = "{} {} ".format(question, default_str)
while True:
choice = input(prompt_str).lower()
if not choice and default is not None:
return default
if choice in yes_list:
return True
if choice in no_list:
return False
notification_str = "Please respond with 'y' or 'n'"
print(notification_str)
def print_envs_table(self, env_list):
columns = []
for env in sorted(env_list, key=lambda item: item.name):
column = collections.OrderedDict()
column['NAME'] = env.name
if self.params.list_ips:
admin_ip = ''
if 'admin' in [node.name for node in env.get_nodes()]:
admin_ip = (env.get_node(name='admin').
get_ip_address_by_network_name('admin'))
column['ADMIN IP'] = admin_ip
if self.params.timestamps:
column['CREATED'] = env.created.strftime(
'%Y-%m-%d_%H:%M:%S')
columns.append(column)
if columns:
self.print_table(headers="keys", columns=columns)
def node_dict(self, node):
return {'name': node.name,
'vnc': node.get_vnc_port()}
def do_list(self):
self.print_envs_table(Environment.list_all())
def do_show(self):
headers = ("VNC", "NODE-NAME")
columns = [(node.get_vnc_port(), node.name)
for node in self.env.get_nodes()]
self.print_table(headers=headers, columns=columns)
def do_erase(self):
self.env.erase()
def get_lifetime_delta(self):
data = self.params.env_lifetime
multipliers = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
if data[-1] not in multipliers:
raise ValueError(
'Value should end with '
'one of "{}", got "{}"'.format(
" ".join(multipliers.keys()), data
))
num = int(data[:-1])
mul = data[-1]
return datetime.timedelta(seconds=num*multipliers[mul])
def get_old_environments(self):
delta = self.get_lifetime_delta()
# devops uses utc timestamps for BaseModel
timestamp_now = datetime.datetime.utcnow()
envs_to_erase = []
env_list = Environment.list_all()
for env in env_list:
if (timestamp_now - env.created) > delta:
envs_to_erase.append(env)
return envs_to_erase
def do_erase_old(self):
envs_to_erase = self.get_old_environments()
for env in envs_to_erase:
print("Env '{}' will be erased!".format(env.name))
if envs_to_erase:
if not self.params.force_cleanup:
answer = self.query_yes_no(
"The cleanup operation is destructive one, "
"all environments listed above will be erased. "
"DELETION CAN NOT BE UNDONE! Proceed? ",
default=False)
if not answer:
print("Wise choice, aborting...")
sys.exit(0)
else:
print("Nothing to erase, exiting...")
sys.exit(0)
for env in envs_to_erase:
print("Erasing '{}'...".format(env.name))
env.erase()
def do_list_old(self):
self.print_envs_table(self.get_old_environments())
def do_start(self):
self.env.start()
def do_destroy(self):
self.env.destroy(verbose=False)
def do_suspend(self):
self.env.suspend(verbose=False)
def do_resume(self):
self.env.resume(verbose=False)
def do_revert(self):
self.env.revert(self.snapshot_name, flag=False)
def do_snapshot(self):
if self.env.has_snapshot(self.snapshot_name):
sys.exit("Snapshot with name {0} already exists."
.format(self.snapshot_name))
else:
self.env.snapshot(self.snapshot_name)
def do_synchronize(self):
Environment.synchronize_all()
def do_snapshot_list(self):
snapshots = collections.OrderedDict()
Snap = collections.namedtuple('Snap', ['info', 'nodes'])
for node in self.env.get_nodes():
for snap in node.get_snapshots():
if snap.name in snapshots:
snapshots[snap.name].nodes.append(node.name)
else:
snapshots[snap.name] = Snap(snap, [node.name, ])
snapshots = sorted(snapshots.values(), key=lambda x: x.info.created)
headers = ('SNAPSHOT', 'CREATED', 'NODES-NAMES')
columns = []
for info, nodes in snapshots:
nodes.sort()
columns.append((
info.name,
info.created.strftime('%Y-%m-%d %H:%M:%S'),
', '.join(nodes),
))
self.print_table(columns=columns, headers=headers)
def do_snapshot_delete(self):
for node in self.env.get_nodes():
snaps = [elem.name for elem in node.get_snapshots()]
if self.snapshot_name in snaps:
node.erase_snapshot(name=self.snapshot_name)
def do_net_list(self):
headers = ("NETWORK NAME", "IP NET")
columns = [(net.name, net.ip_network)
for net in self.env.get_networks()]
self.print_table(headers=headers, columns=columns)
def do_timesync(self):
if not self.env.get_admin_nodes():
print('There is no FuelAdmin node. It is impossible to sync time')
return
if not self.params.node_name:
nodes = [node.name for node in self.env.get_nodes()
if node.driver.node_active(node)]
else:
nodes = [self.params.node_name]
cur_time = sync_time(self.env, nodes, skip_sync=True)
for name in sorted(cur_time):
print("Current time on '{0}' = {1}".format(name, cur_time[name]))
print("Please wait for a few minutes while time is synchronized...")
new_time = sync_time(self.env, nodes, skip_sync=False)
for name in sorted(new_time):
print("New time on '{0}' = {1}".format(name, new_time[name]))
def do_revert_resume(self):
self.env.revert(self.snapshot_name, flag=False)
self.env.resume(verbose=False)
if not self.params.no_timesync:
print('Time synchronization is starting')
self.do_timesync()
def do_version(self):
print(devops.__version__)
def do_create(self):
env_name = self.params.name
for env in Environment.list_all():
if env.name == env_name:
print("Please, set another environment name")
raise SystemExit()
self.env = Environment.create(env_name)
networks, prefix = self.params.net_pool.split(':')
Network.default_pool = Network.create_network_pool(
networks=[IPNetwork(networks)],
prefix=int(prefix))
networks = Network.create_networks(environment=self.env)
for network in networks:
network.define()
print("Created network '{0}' {1}".format(network.name,
network.ip_network))
admin_node = self.admin_add(networks=networks, force_define=True)
self.do_slave_add(force_define=True)
admin_node.disk_devices.get(device='cdrom').volume.upload(
self.params.iso_path)
for net in self.env.get_networks():
net.start()
def do_slave_add(self, force_define=True):
vcpu = self.params.vcpu_count
memory = self.params.ram_size
created_node_names = [n.name for n in self.env.get_nodes()]
def get_available_slave_name():
for i in xrange(1, 1000):
name = "slave-{:02d}".format(i)
if name in created_node_names:
continue
created_node_names.append(name)
return name
for node_num in xrange(self.params.node_count):
node_name = get_available_slave_name()
node = self.env.add_node(name=node_name, vcpu=vcpu, memory=memory)
disknames_capacity = {
'system': 50 * 1024 ** 3
}
if self.params.second_disk_size > 0:
disknames_capacity[
'cinder'] = self.params.second_disk_size * 1024 ** 3
if self.params.third_disk_size > 0:
disknames_capacity[
'swift'] = self.params.third_disk_size * 1024 ** 3
node.attach_disks(
disknames_capacity=disknames_capacity,
force_define=force_define)
node.attach_to_networks()
if force_define is True:
node.define()
print("Created node '{}'".format(node.name))
def do_slave_remove(self):
slaves = [node.name for node in
self.env.get_nodes(role='fuel_slave')]
if self.params.node_name not in slaves:
sys.exit("Node with name {} doesn't exist."
.format(self.params.node_name))
volumes = []
for drive in self.env.get_node(
name=self.params.node_name).disk_devices:
volumes.append(drive.volume)
self.env.get_node(name=self.params.node_name).remove()
for volume in volumes:
volume.erase()
def do_slave_change(self):
node = self.env.get_node(name=self.params.node_name)
node.set_vcpu(vcpu=self.params.vcpu_count)
node.set_memory(memory=self.params.ram_size)
def do_admin_change(self):
node = self.env.get_node(name="admin")
node.set_vcpu(vcpu=self.params.admin_vcpu_count)
node.set_memory(memory=self.params.admin_ram_size)
def do_admin_setup(self):
admin_node = self.env.get_node(name='admin')
admin_node.destroy()
node_manager.admin_prepare_disks(node=admin_node,
disk_size=self.params.admin_disk_size)
admin_node.start()
node_manager.admin_change_config(admin_node=admin_node,
admin_centos_version=6,
static_interface='eth0')
admin_node.await("admin", timeout=10 * 60)
node_manager.admin_wait_bootstrap(3000, self.env)
print("Setup complete.\n ssh {0}@{1}".format(
settings.SSH_CREDENTIALS['login'],
admin_node.get_ip_address_by_network_name(
settings.SSH_CREDENTIALS['admin_network'])))
def do_admin_setup_centos7(self):
admin_node = self.env.get_node(name='admin')
admin_node.destroy()
node_manager.admin_prepare_disks(node=admin_node,
disk_size=self.params.admin_disk_size)
admin_node.start()
node_manager.admin_change_config(admin_node=admin_node,
admin_centos_version=7,
static_interface=self.params.iface)
admin_node.await("admin", timeout=10 * 60)
node_manager.admin_wait_bootstrap(3000, self.env)
print("Setup complete.\n ssh {0}@{1}".format(
settings.SSH_CREDENTIALS['login'],
admin_node.get_ip_address_by_network_name(
settings.SSH_CREDENTIALS['admin_network'])))
def admin_add(self, networks=None, force_define=True):
vcpu = self.params.admin_vcpu_count
ram = self.params.admin_ram_size
iso_path = self.params.iso_path
iso_size = get_file_size(iso_path)
if iso_size <= 0:
print("Please, set correct ISO file")
sys.exit(1)
if networks is None:
networks = []
interfaces = settings.INTERFACE_ORDER
for name in interfaces:
networks.append(self.env.create_networks(name))
node_template = create_admin_config(
admin_vcpu=vcpu,
admin_memory=ram,
admin_sysvolume_capacity=settings.ADMIN_NODE_VOLUME_SIZE,
admin_iso_path=iso_path,
boot_from='cdrom',
interfaceorder=settings.INTERFACE_ORDER)
admin_node = self.env.create_node(node_template)
if force_define is True:
admin_node.define()
print("Created node '{}'".format(admin_node.name))
return admin_node
def do_node_start(self):
self.check_param_show_help(self.params.node_name)
self.env.get_node(name=self.params.node_name).start()
def do_node_destroy(self):
self.check_param_show_help(self.params.node_name)
self.env.get_node(name=self.params.node_name).destroy()
def do_node_reset(self):
self.check_param_show_help(self.params.node_name)
self.env.get_node(name=self.params.node_name).reset()
def check_param_show_help(self, parameter):
if not parameter:
self.args.append('-h')
self.get_params()
commands = {
'list': do_list,
'list-old': do_list_old,
'show': do_show,
'erase': do_erase,
'erase-old': do_erase_old,
'start': do_start,
'destroy': do_destroy,
'suspend': do_suspend,
'resume': do_resume,
'revert': do_revert,
'snapshot': do_snapshot,
'sync': do_synchronize,
'snapshot-list': do_snapshot_list,
'snapshot-delete': do_snapshot_delete,
'net-list': do_net_list,
'time-sync': do_timesync,
'revert-resume': do_revert_resume,
'version': do_version,
'create': do_create,
'slave-add': do_slave_add,
'slave-change': do_slave_change,
'slave-remove': do_slave_remove,
'admin-setup': do_admin_setup,
'admin-setup-centos7': do_admin_setup_centos7,
'admin-change': do_admin_change,
'node-start': do_node_start,
'node-destroy': do_node_destroy,
'node-reset': do_node_reset
}
def get_params(self):
name_parser = argparse.ArgumentParser(add_help=False)
name_parser.add_argument('name', help='environment name',
default=os.environ.get('ENV_NAME'),
metavar='ENV_NAME')
snapshot_name_parser = argparse.ArgumentParser(add_help=False)
snapshot_name_parser.add_argument('snapshot-name',
help='snapshot name',
default=os.environ.get(
'SNAPSHOT_NAME'))
node_name_parser = argparse.ArgumentParser(add_help=False)
node_name_parser.add_argument('--node-name', '-N',
help='node name',
default=None)
no_timesync_parser = argparse.ArgumentParser(add_help=False)
no_timesync_parser.add_argument('--no-timesync', dest='no_timesync',
action='store_const', const=True,
help='revert without timesync',
default=False)
list_ips_parser = argparse.ArgumentParser(add_help=False)
list_ips_parser.add_argument('--ips', dest='list_ips',
action='store_const', const=True,
help='show admin node ip addresses',
default=False)
timestamps_parser = argparse.ArgumentParser(add_help=False)
timestamps_parser.add_argument('--timestamps', dest='timestamps',
action='store_const', const=True,
help='show creation timestamps',
default=False)
iso_path_parser = argparse.ArgumentParser(add_help=False)
iso_path_parser.add_argument('--iso-path', '-I', dest='iso_path',
help='Set Fuel ISO path',
required=True)
admin_ram_parser = argparse.ArgumentParser(add_help=False)
admin_ram_parser.add_argument('--admin-ram', dest='admin_ram_size',
help='Select admin node RAM size (MB)',
default=1536, type=int)
admin_vcpu_parser = argparse.ArgumentParser(add_help=False)
admin_vcpu_parser.add_argument('--admin-vcpu', dest='admin_vcpu_count',
help='Select admin node VCPU count',
default=2, type=int)
change_admin_ram_parser = argparse.ArgumentParser(add_help=False)
change_admin_ram_parser.add_argument('--admin-ram',
dest='admin_ram_size',
help='Select admin node RAM '
'size (MB)',
default=None, type=int)
change_admin_vcpu_parser = argparse.ArgumentParser(add_help=False)
change_admin_vcpu_parser.add_argument('--admin-vcpu',
dest='admin_vcpu_count',
help='Select admin node VCPU '
'count',
default=None, type=int)
admin_disk_size_parser = argparse.ArgumentParser(add_help=False)
admin_disk_size_parser.add_argument('--admin-disk-size',
dest='admin_disk_size',
help='Set admin node disk '
'size (GB)',
default=50, type=int)
admin_setup_iface_parser = argparse.ArgumentParser(add_help=False)
admin_setup_iface_parser.add_argument('--iface',
dest='iface',
help='Static network interface '
'to use when configuring '
'the admin node. Should '
'be eth0 or enp0s3',
default='enp0s3')
ram_parser = argparse.ArgumentParser(add_help=False)
ram_parser.add_argument('--ram', dest='ram_size',
help='Set node RAM size',
default=1024, type=int)
vcpu_parser = argparse.ArgumentParser(add_help=False)
vcpu_parser.add_argument('--vcpu', dest='vcpu_count',
help='Set node VCPU count',
default=1, type=int)
change_ram_parser = argparse.ArgumentParser(add_help=False)
change_ram_parser.add_argument('--ram', dest='ram_size',
help='Set node RAM size',
default=None, type=int)
change_vcpu_parser = argparse.ArgumentParser(add_help=False)
change_vcpu_parser.add_argument('--vcpu', dest='vcpu_count',
help='Set node VCPU count',
default=None, type=int)
node_count = argparse.ArgumentParser(add_help=False)
node_count.add_argument('--node-count', '-C', dest='node_count',
help='How many nodes will be created',
default=1, type=int)
net_pool = argparse.ArgumentParser(add_help=False)
net_pool.add_argument('--net-pool', '-P', dest='net_pool',
help='Set ip network pool (cidr)',
default="10.21.0.0/16:24", type=str)
second_disk_size = argparse.ArgumentParser(add_help=False)
second_disk_size.add_argument('--second-disk-size',
dest='second_disk_size',
help='Allocate second disk for node '
'with selected size(GB). '
'If set to 0, the disk will not be '
'allocated',
default=50, type=int)
third_disk_size = argparse.ArgumentParser(add_help=False)
third_disk_size.add_argument('--third-disk-size',
dest='third_disk_size',
help='Allocate the third disk for node '
'with selected size(GB). '
'If set to 0, the disk will not be '
'allocated',
default=50, type=int)
force_cleanup_parser = argparse.ArgumentParser(add_help=False)
force_cleanup_parser.add_argument(
'--force-cleanup',
dest='force_cleanup',
action='store_const', const=True,
help='Do not ask confirmation for cleanup action.',
default=False)
env_lifetime = argparse.ArgumentParser(add_help=False)
env_lifetime.add_argument(
dest='env_lifetime',
help='Erase environments older than given time interval. '
'Example:"45m", "12h", "3d"',
default="", type=str)
parser = argparse.ArgumentParser(
description="Manage virtual environments. "
"For additional help, use with -h/--help option")
subparsers = parser.add_subparsers(title="Operation commands",
help='available commands',
dest='command')
subparsers.add_parser('list',
parents=[list_ips_parser, timestamps_parser],
help="Show virtual environments",
description="Show virtual environments on host")
subparsers.add_parser('erase-old',
parents=[force_cleanup_parser,
env_lifetime],
help="Cleanup old virtual environments",
description="Cleanup virtual environments on "
"host")
subparsers.add_parser('list-old',
parents=[env_lifetime, list_ips_parser,
timestamps_parser],
help="Show virtual environments older than given"
" lifetime interval",
description="Show old virtual "
"environments on host")
subparsers.add_parser('show', parents=[name_parser],
help="Show VMs in environment",
description="Show VMs in environment")
subparsers.add_parser('erase', parents=[name_parser],
help="Delete environment",
description="Delete environment and VMs on it")
subparsers.add_parser('start', parents=[name_parser],
help="Start VMs",
description="Start VMs in selected environment")
subparsers.add_parser('destroy', parents=[name_parser],
help="Destroy(stop) VMs",
description="Stop VMs in selected environment")
subparsers.add_parser('suspend', parents=[name_parser],
help="Suspend VMs",
description="Suspend VMs in selected "
"environment")
subparsers.add_parser('resume', parents=[name_parser],
help="Resume VMs",
description="Resume VMs in selected environment")
subparsers.add_parser('revert',
parents=[name_parser, snapshot_name_parser],
help="Apply snapshot to environment",
description="Apply selected snapshot to "
"environment")
subparsers.add_parser('snapshot',
parents=[name_parser, snapshot_name_parser],
help="Make environment snapshot",
description="Make environment snapshot")
subparsers.add_parser('sync',
help="Synchronization environment and devops",
description="Synchronization environment "
"and devops"),
subparsers.add_parser('snapshot-list',
parents=[name_parser],
help="Show snapshots in environment",
description="Show snapshots in selected "
"environment")
subparsers.add_parser('snapshot-delete',
parents=[name_parser, snapshot_name_parser],
help="Delete snapshot from environment",
description="Delete snapshot from selected "
"environment")
subparsers.add_parser('net-list',
parents=[name_parser],
help="Show networks in environment",
description="Display allocated networks for "
"environment")
subparsers.add_parser('time-sync',
parents=[name_parser, node_name_parser],
help="Sync time on all env nodes",
description="Sync time on all active nodes "
"of environment starting from "
"admin")
subparsers.add_parser('revert-resume',
parents=[name_parser, snapshot_name_parser,
node_name_parser, no_timesync_parser],
help="Revert, resume, sync time on VMs",
description="Revert and resume VMs in selected"
"environment, then"
" sync time on VMs")
subparsers.add_parser('version',
help="Show devops version")
subparsers.add_parser('create',
parents=[name_parser, vcpu_parser,
node_count, ram_parser,
net_pool, iso_path_parser,
admin_disk_size_parser,
admin_ram_parser,
admin_vcpu_parser,
second_disk_size,
third_disk_size],
help="Create a new environment",
description="Create an environment with "
"the Fuel Master node and slaves"),
subparsers.add_parser('slave-add',
parents=[name_parser, node_count,
ram_parser, vcpu_parser,
second_disk_size, third_disk_size],
help="Add a node",
description="Add a new node to environment")
subparsers.add_parser('slave-change',
parents=[name_parser, node_name_parser,
change_ram_parser, change_vcpu_parser],
help="Change node VCPU and memory config",
description="Change count of VCPUs and memory")
subparsers.add_parser('slave-remove',
parents=[name_parser, node_name_parser],
help="Remove node from environment",
description="Remove selected node from "
"environment")
subparsers.add_parser('admin-setup',
parents=[name_parser, admin_disk_size_parser],
help="Setup admin node",
description="Setup admin node from ISO")
subparsers.add_parser('admin-setup-centos7',
parents=[name_parser, admin_disk_size_parser,
admin_setup_iface_parser],
help="Setup CentOS 7 based admin node",
description="Setup admin node from ISO")
subparsers.add_parser('admin-change',
parents=[name_parser, change_admin_ram_parser,
change_admin_vcpu_parser],
help="Change admin node VCPU and memory config",
description="Change count of VCPUs and memory "
"for admin node")
subparsers.add_parser('node-start',
parents=[name_parser, node_name_parser],
help="Start node in environment",
description="Start a separate node in "
"environment")
subparsers.add_parser('node-destroy',
parents=[name_parser, node_name_parser],
help="Destroy (power off) node in environment",
description="Destroy a separate node in "
"environment")
subparsers.add_parser('node-reset',
parents=[name_parser, node_name_parser],
help="Reset (restart) node in environment",
description="Reset a separate node in "
"environment")
if len(self.args) == 0:
self.args = ['-h']
return parser.parse_args(self.args)
def main(args=None):
if args is None:
args = sys.argv[1:]
Shell(args).execute()