Add cleanup action

We can use fuel-devops for deletion of outdated environment.
Usage:
  dos.py show-old 3d
  dos.py erase-old 3d
where "3d" is time-to-live interval. All envs older than
given interval will be erased.

Last char of interval is multiplier, could be:
 - s: seconds
 - m: minutes
 - h: hours
 - d: days

erase-old operation is interactive but it could be
disabled via "--force-cleanup" arg.

list-old command supports --timestamps arg. Note that devops uses
UTC timestamps!

Change-Id: Ic1a744996495296e22ae0fdef9752dba8790aac8
This commit is contained in:
Vladimir Khlyunev 2017-09-04 11:14:31 +04:00
parent 887368db3b
commit 3ac4e8fc7b
1 changed files with 133 additions and 3 deletions

View File

@ -19,8 +19,11 @@ import collections
import os
import sys
import datetime
import tabulate
from six.moves import input
import devops
from devops import client
from devops import error
@ -53,10 +56,53 @@ class Shell(object):
print(tabulate.tabulate(columns, headers=headers,
tablefmt="simple"))
def do_list(self):
env_names = self.client.list_env_names()
@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_names_list):
columns = []
for env_name in sorted(env_names):
for env_name in sorted(env_names_list):
env = self.client.get_env(env_name)
column = collections.OrderedDict()
column['NAME'] = env.name
@ -72,6 +118,9 @@ class Shell(object):
self.print_table(headers='keys', columns=columns)
def do_list(self):
self.print_envs_table(self.client.list_env_names())
def do_show(self):
nodes = sorted(self.env.get_nodes(), key=lambda node: node.name)
headers = ("VNC", "NODE-NAME", "GROUP-NAME")
@ -131,6 +180,58 @@ class Shell(object):
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 = []
for env_name in client.DevopsClient.list_env_names():
env = client.DevopsClient.get_env(env_name)
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):
env_names = [env.name for env in self.get_old_environments()]
self.print_envs_table(env_names)
def do_start(self):
self.env.start()
@ -475,6 +576,22 @@ class Shell(object):
'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")
@ -485,6 +602,19 @@ class Shell(object):
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")