Updated management commands

This commit is contained in:
dvanaken 2019-11-25 22:50:16 -05:00 committed by Dana Van Aken
parent 4ab2fdfd52
commit 9994a22f16
7 changed files with 126 additions and 57 deletions

View File

@ -9,6 +9,8 @@ local_settings.py
celerybeat-schedule* celerybeat-schedule*
*.pid *.pid
# Raw data files # # Management commands #
################## #######################
data/media/* debug_*.tar.gz
session_knobs.json
dump_website.json

View File

@ -6,6 +6,8 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from website.utils import create_user # pylint: disable=no-name-in-module,import-error
class Command(BaseCommand): class Command(BaseCommand):
help = 'Create a new user.' help = 'Create a new user.'
@ -31,22 +33,16 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
username = options['username'] username = options['username']
if User.objects.filter(username=username).exists():
self.stdout.write(self.style.NOTICE(
"ERROR: User '{}' already exists.".format(username)))
else:
password = options['password'] password = options['password']
email = options['email'] email = options['email']
superuser = options['superuser'] superuser = options['superuser']
if superuser: _, created = create_user(username, password, email, superuser)
email = email or '{}@noemail.com'.format(username)
create_user = User.objects.create_superuser
else:
create_user = User.objects.create_user
create_user(username=username, password=password, email=email)
if created:
self.stdout.write(self.style.SUCCESS("Successfully created {} '{}'{}.".format( self.stdout.write(self.style.SUCCESS("Successfully created {} '{}'{}.".format(
'superuser' if superuser else 'user', username, 'superuser' if superuser else 'user', username,
" ('{}')".format(email) if email else ''))) " ('{}')".format(email) if email else '')))
else:
self.stdout.write(self.style.NOTICE(
"ERROR: User '{}' already exists.".format(username)))

View File

@ -6,6 +6,8 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from website.utils import delete_user # pylint: disable=no-name-in-module,import-error
class Command(BaseCommand): class Command(BaseCommand):
help = 'Delete an existing user.' help = 'Delete an existing user.'
@ -19,11 +21,10 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
username = options['username'] username = options['username']
try: _, deleted = delete_user(username)
user = User.objects.get(username=username) if deleted:
user.delete()
self.stdout.write(self.style.SUCCESS( self.stdout.write(self.style.SUCCESS(
"Successfully deleted user '{}'.".format(username))) "Successfully deleted user '{}'.".format(username)))
except User.DoesNotExist: else:
self.stdout.write(self.style.NOTICE( self.stdout.write(self.style.NOTICE(
"ERROR: User '{}' does not exist.".format(username))) "ERROR: User '{}' does not exist.".format(username)))

View File

@ -5,6 +5,7 @@
# #
import json import json
import os import os
from collections import OrderedDict
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
@ -22,13 +23,18 @@ class Command(BaseCommand):
parser.add_argument( parser.add_argument(
'-f', '--filename', '-f', '--filename',
metavar='FILE', metavar='FILE',
default='session_knobs.json',
help='Name of the file to write the session knob tunability to. ' help='Name of the file to write the session knob tunability to. '
'Default: knob.json') 'Default: session_knobs.json')
parser.add_argument( parser.add_argument(
'-d', '--directory', '-d', '--directory',
metavar='DIR', metavar='DIR',
help='Path of the directory to write the session knob tunability to. ' help='Path of the directory to write the session knob tunability to. '
'Default: current directory') 'Default: current directory')
parser.add_argument(
'--tunable-only',
action='store_true',
help='Dump tunable knobs only. Default: False')
def handle(self, *args, **options): def handle(self, *args, **options):
directory = options['directory'] or '' directory = options['directory'] or ''
@ -40,13 +46,13 @@ class Command(BaseCommand):
raise CommandError( raise CommandError(
"ERROR: Session with upload code '{}' not exist.".format(options['uploadcode'])) "ERROR: Session with upload code '{}' not exist.".format(options['uploadcode']))
session_knobs = SessionKnobManager.get_knob_min_max_tunability(session) session_knobs = SessionKnobManager.get_knob_min_max_tunability(
session, tunable_only=options['tunable_only'])
filename = options['filename'] or 'knobs.json' path = os.path.join(directory, options['filename'])
path = os.path.join(directory, filename)
with open(path, 'w') as f: with open(path, 'w') as f:
json.dump(session_knobs, f, indent=4) json.dump(OrderedDict(sorted(session_knobs.items())), f, indent=4)
self.stdout.write(self.style.SUCCESS( self.stdout.write(self.style.SUCCESS(
"Successfully dumped knob information to '{}'.".format(path))) "Successfully dumped knob information to '{}'.".format(path)))

View File

@ -5,14 +5,38 @@
# #
import json import json
import os import os
from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from website.models import Session, SessionKnob, SessionKnobManager from website.models import Session, SessionKnob, SessionKnobManager
HELP = """
Load knobs for the session with the given upload code.
example of JSON file format:
{
"global.knob1": {
"minval": 0,
"maxval": 100,
"tunable": true
},
"global.knob2": {
"minval": 1000000,
"maxval": 2000000,
"tunable": false
}
}
"""
class Command(BaseCommand): class Command(BaseCommand):
help = 'load knobs for the session with the given upload code.' help = HELP
def create_parser(self, prog_name, subcommand):
parser = super(Command, self).create_parser(prog_name, subcommand)
parser.formatter_class = RawTextHelpFormatter
return parser
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument( parser.add_argument(
@ -22,31 +46,40 @@ class Command(BaseCommand):
parser.add_argument( parser.add_argument(
'-f', '--filename', '-f', '--filename',
metavar='FILE', metavar='FILE',
default='session_knobs.json',
help='Name of the file to read the session knob tunability from. ' help='Name of the file to read the session knob tunability from. '
'Default: knob.json') 'Default: session_knobs.json')
parser.add_argument( parser.add_argument(
'-d', '--directory', '-d', '--directory',
metavar='DIR', metavar='DIR',
help='Path of the directory to read the session knob tunability from. ' help='Path of the directory to read the session knob tunability from. '
'Default: current directory') 'Default: current directory')
parser.add_argument(
'--disable-others',
action='store_true',
help='Disable the knob tunability of all session knobs NOT included '
'in the JSON file. Default: False')
def handle(self, *args, **options): def handle(self, *args, **options):
directory = options['directory'] or '' directory = options['directory'] or ''
if directory and not os.path.exists(directory): path = os.path.join(directory, filename)
os.makedirs(directory)
try:
with open(path, 'r') as f:
knobs = json.load(f)
except FileNotFoundError:
raise CommandError("ERROR: File '{}' does not exist.".format(path))
except json.decoder.JSONDecodeError:
raise CommandError("ERROR: Unable to decode JSON file '{}'.".format(path))
try: try:
session = Session.objects.get(upload_code=options['uploadcode']) session = Session.objects.get(upload_code=options['uploadcode'])
except Session.DoesNotExist: except Session.DoesNotExist:
raise CommandError( raise CommandError(
"ERROR: Session with upload code '{}' not exist.".format(options['uploadcode'])) "ERROR: Session with upload code '{}' not exist.".format(options['uploadcode']))
filename = options['filename'] or 'knobs.json' SessionKnobManager.set_knob_min_max_tunability(
path = os.path.join(directory, filename) session, knobs, disable_others=options['disable_others'])
with open(path, 'r') as f:
knobs = json.load(f)
SessionKnobManager.set_knob_min_max_tunability(session, knobs)
self.stdout.write(self.style.SUCCESS( self.stdout.write(self.style.SUCCESS(
"Successfully load knob information from '{}'.".format(path))) "Successfully load knob information from '{}'.".format(path)))

View File

@ -3,6 +3,8 @@
# #
# Copyright (c) 2017-18, Carnegie Mellon University Database Group # Copyright (c) 2017-18, Carnegie Mellon University Database Group
# #
from django import db
from django.core.management import call_command
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from fabric.api import local from fabric.api import local
from website.settings import DATABASES from website.settings import DATABASES
@ -11,40 +13,66 @@ from website.settings import DATABASES
class Command(BaseCommand): class Command(BaseCommand):
help = 'dump the website; reset the website; load data from file if specified.' help = 'dump the website; reset the website; load data from file if specified.'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
engine = DATABASES['default']['ENGINE']
user = DATABASES['default']['USER']
passwd = DATABASES['default']['PASSWORD']
host = DATABASES['default']['HOST']
port = DATABASES['default']['PORT']
if engine.endswith('mysql'):
db_cmd_fmt = 'mysql -u {user} -p{passwd} -h {host} -P {port} -N -B -e "{{cmd}}"'
elif engine.endswith('postgresql'):
db_cmd_fmt = 'PGPASSWORD={passwd} psql -U {user} -h {host} -p {port} -c "{{cmd}}"'
else:
raise NotImplementedError("Database engine '{}' is not implemented.".format(engine))
self._db_cmd_fmt = db_cmd_fmt.format(user=user, passwd=passwd, host=host, port=port).format
def call_db_command(self, cmd):
local(self._db_cmd_fmt(cmd=cmd))
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument( parser.add_argument(
'-d', '--dumpfile', '-d', '--dumpfile',
metavar='FILE', metavar='FILE',
help='Name of the file to dump data to. ' default='dump_website.json',
'Default: dump_website.json') help="Name of the file to dump data to. "
"Default: 'dump_website.json'")
parser.add_argument( parser.add_argument(
'-l', '--loadfile', '-l', '--loadfile',
metavar='FILE', metavar='FILE',
help='Name of the file to load data from. ') help="Name of the file to load data from. "
"Default: '' (no data loaded)")
def reset_website(self): def reset_website(self):
# WARNING: destroys the existing website and creates with all # WARNING: destroys the existing website and creates with all
# of the required inital data loaded (e.g., the KnobCatalog) # of the required inital data loaded (e.g., the KnobCatalog)
# Recreate the ottertune database # Recreate the ottertune database
user = DATABASES['default']['USER'] db.connections.close_all()
passwd = DATABASES['default']['PASSWORD'] dbname = DATABASES['default']['NAME']
name = DATABASES['default']['NAME'] self.call_db_command("DROP DATABASE IF EXISTS {}".format(dbname))
local("mysql -u {} -p{} -N -B -e \"DROP DATABASE IF EXISTS {}\"".format( self.call_db_command("CREATE DATABASE {}".format(dbname))
user, passwd, name))
local("mysql -u {} -p{} -N -B -e \"CREATE DATABASE {}\"".format(
user, passwd, name))
# Reinitialize the website # Reinitialize the website
local('python manage.py migrate') call_command('makemigrations', 'website')
call_command('migrate')
call_command('startcelery')
def handle(self, *args, **options): def handle(self, *args, **options):
dumpfile = options['dumpfile'] if options['dumpfile'] else 'dump_website.json' dumpfile = options['dumpfile']
local("python manage.py dumpdata admin auth django_db_logger djcelery sessions\ self.stdout.write("Dumping database to file '{}'...".format(dumpfile))
sites website > {}".format(dumpfile)) call_command('dumpdata', 'admin', 'auth', 'django_db_logger', 'djcelery', 'sessions',
self.reset_website() 'sites', 'website', output=dumpfile)
if options['loadfile']: call_command('stopcelery')
local("python manage.py loaddata '{}'".format(options['loadfile']))
self.stdout.write(self.style.SUCCESS( self.reset_website()
"Successfully reset website."))
loadfile = options['loadfile']
if loadfile:
self.stdout.write("Loading database from file '{}'...".format(loadfile))
call_command('loaddata', loadfile)
self.stdout.write(self.style.SUCCESS("Successfully reset website."))

View File

@ -1005,9 +1005,12 @@ def give_result(request, upload_code): # pylint: disable=unused-argument
# If the task status was incomplete when we first queried latest_result # If the task status was incomplete when we first queried latest_result
# but succeeded before the call to TaskUtil.get_task_status() finished # but succeeded before the call to TaskUtil.get_task_status() finished
# then latest_result is stale and must be updated. # then latest_result is stale and must be updated.
LOG.debug("Updating stale result (pk=%s)", latest_result.pk)
latest_result = Result.objects.get(id=latest_result.pk) latest_result = Result.objects.get(id=latest_result.pk)
if not latest_result.next_configuration: if not latest_result.next_configuration:
LOG.warning("Failed to get the next configuration from the latest result: %s",
model_to_dict(latest_result))
overall_status = 'FAILURE' overall_status = 'FAILURE'
response = _failed_response(latest_result, tasks, num_completed, overall_status, response = _failed_response(latest_result, tasks, num_completed, overall_status,
'Failed to get the next configuration.') 'Failed to get the next configuration.')