fuel-devops/devops/shell.py

581 lines
27 KiB
Python

# Copyright 2013 - 2016 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 tabulate
import devops
from devops import client
from devops import error
from devops.helpers import helpers
from devops import logger
class Shell(object):
def __init__(self, args):
self.args = args
self.params = self.get_params()
self.client = client.DevopsClient()
self.env = None
name = getattr(self.params, 'name', None)
command = getattr(self.params, 'command', None)
if name and command != 'create':
self.env = self.client.get_env(name)
def execute(self):
command_name = 'do_{}'.format(self.params.command.replace('-', '_'))
command_method = getattr(self, command_name)
command_method()
@staticmethod
def print_table(headers, columns):
if not columns:
return
print(tabulate.tabulate(columns, headers=headers,
tablefmt="simple"))
def do_list(self):
env_names = self.client.list_env_names()
columns = []
for env_name in sorted(env_names):
env = self.client.get_env(env_name)
column = collections.OrderedDict()
column['NAME'] = env.name
if self.params.list_ips:
if env.has_admin():
column['ADMIN IP'] = env.get_admin_ip()
else:
column['ADMIN IP'] = ''
if self.params.timestamps:
column['CREATED'] = helpers.utc_to_local(env.created).strftime(
'%Y-%m-%d_%H:%M:%S')
columns.append(column)
self.print_table(headers='keys', columns=columns)
def do_show(self):
nodes = sorted(self.env.get_nodes(), key=lambda node: node.name)
headers = ("VNC", "NODE-NAME", "GROUP-NAME")
columns = [(node.get_vnc_port(), node.name, node.group.name)
for node in nodes]
self.print_table(headers=headers, columns=columns)
def do_erase(self):
self.env.erase()
def do_start(self):
self.env.start()
def do_destroy(self):
self.env.destroy()
def do_suspend(self):
self.env.suspend()
def do_resume(self):
self.env.resume()
def do_revert(self):
self.env.revert(self.params.snapshot_name, flag=False)
def do_snapshot(self):
self.env.snapshot(self.params.snapshot_name)
def do_sync(self):
self.client.synchronize_all()
def do_snapshot_list(self):
snapshots = collections.OrderedDict()
# noinspection PyPep8Naming
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,
helpers.utc_to_local(
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 = [x.name for x in node.get_snapshots()]
if self.params.snapshot_name in snaps:
node.erase_snapshot(name=self.params.snapshot_name)
def do_net_list(self):
headers = ("NETWORK NAME", "IP NET")
columns = [(net.name, net.ip_network)
for net in self.env.get_address_pools()]
self.print_table(headers=headers, columns=columns)
def do_slave_ip_list(self):
address_pool_name = self.params.address_pool_name
slave_ips = {}
for l2dev in self.env.get_env_l2_network_devices():
if l2dev.address_pool is None:
continue
if address_pool_name and \
l2dev.address_pool.name != address_pool_name:
continue
ap_slave_ips = []
for node in self.env.get_nodes():
if self.params.ip_only:
ap_slave_ips.append(
node.get_ip_address_by_network_name(l2dev.name))
else:
ap_slave_ips.append(
"{0},{1}".format(
node.name,
node.get_ip_address_by_network_name(l2dev.name)))
if ap_slave_ips:
slave_ips[l2dev.address_pool.name] = ap_slave_ips
if not slave_ips:
sys.exit('No IPs were allocated for environment!')
for ap, n_ips in sorted(slave_ips.items()):
if address_pool_name:
print(' '.join(n_ips))
else:
print(ap + ": " + ' '.join(n_ips))
def do_time_sync(self):
if not self.env.has_admin():
print('There is no FuelAdmin node. It is impossible to sync time')
return
node_name = self.params.node_name
node_names = [node_name] if node_name else None
cur_time = self.env.get_curr_time(node_names)
for name in sorted(cur_time):
print('Current time on {0!r} = {1}'.format(name, cur_time[name]))
print('Please wait for a few minutes while time is synchronized...')
new_time = self.env.sync_time(node_names)
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.params.snapshot_name, flag=False)
self.env.resume()
if not self.params.no_timesync:
print('Time synchronization is starting')
self.do_time_sync()
@staticmethod
def do_version():
print(devops.__version__)
def do_create(self):
"""Create env using cli parameters."""
env = self.client.create_env(
env_name=self.params.name,
admin_iso_path=self.params.iso_path,
admin_vcpu=self.params.admin_vcpu_count,
admin_memory=self.params.admin_ram_size,
admin_sysvolume_capacity=self.params.admin_disk_size,
nodes_count=self.params.node_count,
slave_vcpu=self.params.vcpu_count,
slave_memory=self.params.ram_size,
second_volume_capacity=self.params.second_disk_size,
third_volume_capacity=self.params.third_disk_size,
net_pool=self.params.net_pool.split(':'),
)
env.define()
def do_create_env(self):
"""Create env using config file."""
env = self.client.create_env_from_config(
self.params.env_config_name)
env.define()
def do_slave_add(self):
self.env.add_slaves(
nodes_count=self.params.node_count,
slave_vcpu=self.params.vcpu_count,
slave_memory=self.params.ram_size,
second_volume_capacity=self.params.second_disk_size,
third_volume_capacity=self.params.third_disk_size,
)
def do_slave_remove(self):
# TODO(astudenov): add positional argument instead of option
node = self.env.get_node(name=self.params.node_name)
node.remove()
def do_slave_change(self):
node = self.env.get_node(name=self.params.node_name)
# TODO(astudenov): check if node is under libvirt controll
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")
# TODO(astudenov): check if node is under libvirt controll
node.set_vcpu(vcpu=self.params.admin_vcpu_count)
node.set_memory(memory=self.params.admin_ram_size)
def do_admin_setup(self):
# start networks first
for group in self.env.get_groups():
group.start_networks()
self.env.admin_setup(
boot_from=self.params.boot_from,
iface=self.params.iface)
print('Setup complete.\n ssh {0}@{1}'.format(
self.env.get_admin_login(),
self.env.get_admin_ip()))
def do_node_start(self):
# TODO(astudenov): add positional argument instead of
# checking that option is present
self.check_param_show_help(self.params.node_name)
self.env.get_node(name=self.params.node_name).start()
def do_node_destroy(self):
# TODO(astudenov): add positional argument instead of
# checking that option is present
self.check_param_show_help(self.params.node_name)
self.env.get_node(name=self.params.node_name).destroy()
def do_node_reset(self):
# TODO(astudenov): add positional argument instead of
# checking that option is present
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()
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')
group_name_parser = argparse.ArgumentParser(add_help=False)
group_name_parser.add_argument('--group-name', help='group name',
default='default')
env_config_name_parser = argparse.ArgumentParser(add_help=False)
env_config_name_parser.add_argument('env_config_name',
help='environment template name',
default=os.environ.get(
'DEVOPS_SETTINGS_TEMPLATE'))
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')
admin_setup_boot_from_parser = argparse.ArgumentParser(add_help=False)
admin_setup_boot_from_parser.add_argument(
'--boot-from', dest='boot_from', default='cdrom',
help='Set device to boot from for admin node. '
'Should be cdrom or usb')
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)
address_pool_name = argparse.ArgumentParser(add_help=False)
address_pool_name.add_argument(
'--address-pool-name', '-A',
dest='address_pool_name',
help='Specified address pool for printing IPs',
default=None, type=str)
ip_only_parser = argparse.ArgumentParser(add_help=False)
ip_only_parser.add_argument('--ip-only', dest='ip_only',
action='store_const', const=True,
help='Print just IP addresses',
default=False)
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)
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('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('slave-ip-list',
parents=[name_parser,
address_pool_name,
ip_only_parser],
help="Show slave node IPs in environment",
description="Display allocated IPs for "
"environment slave nodes")
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 (DEPRECATED)",
description="Create an environment by using "
"cli options"),
subparsers.add_parser('create-env',
parents=[env_config_name_parser],
help="Create a new environment",
description="Create an environment from a "
"template file"),
subparsers.add_parser('slave-add',
parents=[name_parser, node_count,
ram_parser, vcpu_parser,
second_disk_size, third_disk_size,
group_name_parser],
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_setup_iface_parser,
admin_setup_boot_from_parser],
help="Setup 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:]
try:
shell = Shell(args)
shell.execute()
except error.DevopsError as exc:
logger.debug(exc, exc_info=True)
sys.exit('Error: {}'.format(exc))