Driver now works when the database system is local, remote, or on docker

This commit is contained in:
dvanaken 2019-11-04 22:47:19 -05:00 committed by Dana Van Aken
parent 8ee52a64bf
commit 6283186d76
12 changed files with 285 additions and 102 deletions

View File

@ -30,4 +30,5 @@ lib/
# controller configuration files
config/*
!config/sample_*_config.json
*.pid

View File

@ -43,6 +43,9 @@ DB_PORT = '5432'
# Path to the configuration file on the database server
DB_CONF = '/etc/postgresql/9.6/main/postgresql.conf'
# Path to the directory for storing database dump files
DB_DUMP_DIR = '/var/lib/postgresql/9.6/main/dumpfiles'
# Base config settings to always include when installing new configurations
BASE_DB_CONF = {
'track_counts': 'on',
@ -69,8 +72,7 @@ ORACLE_AWR_ENABLED = False
#==========================================================
# Path to this driver
DRIVER_HOME = os.path.realpath(__file__)
print('DRIVER HOME: {}'.format(DRIVER_HOME))
DRIVER_HOME = os.path.dirname(os.path.realpath(__file__))
# Path to the directory for storing results
RESULT_DIR = os.path.join(DRIVER_HOME, 'results')

View File

@ -20,10 +20,18 @@ import logging
from logging.handlers import RotatingFileHandler
import requests
from fabric.api import env, local, task, lcd
from fabric.api import env, lcd, local, settings, show, task
from fabric.state import output as fabric_output
import driver_config as dconf
from utils import file_exists, get, load_driver_conf, parse_bool, put, run, sudo
fabric_output.update({
'running': True,
'stdout': True,
})
# Loads the driver config file (default: driver_config.py)
dconf = load_driver_conf()
def _setup():
@ -53,22 +61,22 @@ def _setup():
raise ValueError(("Invalid HOST_CONN: {}. Valid values are "
"'local', 'remote', or 'docker'.").format(dconf.HOST_CONN))
# Create all output directories
for d in (dconf.RESULT_DIR, dconf.LOG_DIR, dconf.DB_DUMP_DIR):
# Update Fabric's host list
env.hosts = [LOGIN]
# Create local directories
for d in (dconf.RESULT_DIR, dconf.LOG_DIR, dconf.TEMP_DIR):
os.makedirs(d, exist_ok=True)
# Copy Oracle scripts
if dconf.DB_TYPE == 'oracle':
put('./oracleScripts', '/home/oracle')
sudo('chown -R oracle:oinstall /home/oracle/oracleScripts')
_setup()
# Fabric environment settings
env.hosts = [LOGIN]
fabric_output.update({
'running': True,
'stdout': True,
})
# Configure logging
LOG = logging.getLogger(__name__)
LOG.setLevel(getattr(logging, dconf.LOG_LEVEL, logging.DEBUG))
@ -84,19 +92,13 @@ FileHandler.setFormatter(Formatter)
LOG.addHandler(FileHandler)
def _parse_bool(value):
if not isinstance(value, bool):
value = str(value).lower() == 'true'
return value
@task
def check_disk_usage():
partition = dconf.DATABASE_DISK
disk_use = 0
if partition:
cmd = "df -h {}".format(partition)
out = local(cmd, capture=True).splitlines()[1]
out = run(cmd).splitlines()[1]
m = re.search(r'\d+(?=%)', out)
if m:
disk_use = int(m.group(0))
@ -106,22 +108,21 @@ def check_disk_usage():
@task
def check_memory_usage():
cmd = 'free -m -h'
local(cmd)
run('free -m -h')
@task
def create_controller_config():
if dconf.DB_TYPE == 'postgres':
dburl_fmt = 'jdbc:postgresql://localhost:5432/{db}'.format
dburl_fmt = 'jdbc:postgresql://{host}:{port}/{db}'.format
elif dconf.DB_TYPE == 'oracle':
dburl_fmt = 'jdbc:oracle:thin:@localhost:1521:{db}'.format
dburl_fmt = 'jdbc:oracle:thin:@{host}:{port}:{db}'.format
else:
raise Exception("Database Type {} Not Implemented !".format(dconf.DB_TYPE))
config = dict(
database_type=dconf.DB_TYPE,
database_url=dburl_fmt(db=dconf.DB_NAME),
database_url=dburl_fmt(host=dconf.DB_HOST, port=dconf.DB_PORT, db=dconf.DB_NAME),
username=dconf.DB_USER,
password=dconf.DB_PASSWORD,
upload_code='DEPRECATED',
@ -136,34 +137,60 @@ def create_controller_config():
@task
def restart_database():
if dconf.DB_TYPE == 'postgres':
cmd = 'sudo -u postgres pg_ctl -D {} -w -t 600 restart -m fast'.format(dconf.PG_DATADIR)
if dconf.HOST_CONN == 'docker':
# Restarting the docker container here is the cleanest way to do it
# becaues there's no init system running and the only process running
# in the container is postgres itself
local('docker restart {}'.format(dconf.CONTAINER_NAME))
else:
sudo('pg_ctl -D {} -w -t 600 restart -m fast'.format(dconf.PG_DATADIR), user=dconf.DB_USER)
elif dconf.DB_TYPE == 'oracle':
cmd = 'sh oracleScripts/shutdownOracle.sh && sh oracleScripts/startupOracle.sh'
run('sh oracleScripts/shutdownOracle.sh && sh oracleScripts/startupOracle.sh')
else:
raise Exception("Database Type {} Not Implemented !".format(dconf.DB_TYPE))
local(cmd)
@task
def drop_database():
if dconf.DB_TYPE == 'postgres':
cmd = "PGPASSWORD={} dropdb -e --if-exists {} -U {}".\
format(dconf.DB_PASSWORD, dconf.DB_NAME, dconf.DB_USER)
run("PGPASSWORD={} dropdb -e --if-exists {} -U {} -h {}".format(
dconf.DB_PASSWORD, dconf.DB_NAME, dconf.DB_USER, dconf.DB_HOST))
else:
raise Exception("Database Type {} Not Implemented !".format(dconf.DB_TYPE))
local(cmd)
@task
def create_database():
if dconf.DB_TYPE == 'postgres':
cmd = "PGPASSWORD={} createdb -e {} -U {}".\
format(dconf.DB_PASSWORD, dconf.DB_NAME, dconf.DB_USER)
run("PGPASSWORD={} createdb -e {} -U {} -h {}".format(
dconf.DB_PASSWORD, dconf.DB_NAME, dconf.DB_USER, dconf.DB_HOST))
else:
raise Exception("Database Type {} Not Implemented !".format(dconf.DB_TYPE))
local(cmd)
@task
def create_user():
if dconf.DB_TYPE == 'postgres':
sql = "CREATE USER {} SUPERUSER PASSWORD '{}';".format(dconf.DB_USER, dconf.DB_PASSWORD)
run("PGPASSWORD={} psql -c \\\"{}\\\" -U postgres -h {}".format(
dconf.DB_PASSWORD, sql, dconf.DB_USER, dconf.DB_HOST))
elif dconf.DB_TYPE == 'oracle':
run('sh oracleScripts/createUser.sh {} {}'.format(dconf.DB_USER, dconf.DB_PASSWORD))
else:
raise Exception("Database Type {} Not Implemented !".format(dconf.DB_TYPE))
@task
def drop_user():
if dconf.DB_TYPE == 'postgres':
sql = "DROP USER IF EXISTS {};".format(dconf.DB_USER)
run("PGPASSWORD={} psql -c \\\"{}\\\" -U postgres -h {}".format(
dconf.DB_PASSWORD, sql, dconf.DB_HOST))
elif dconf.DB_TYPE == 'oracle':
run('sh oracleScripts/dropUser.sh {}'.format(dconf.DB_USER))
else:
raise Exception("Database Type {} Not Implemented !".format(dconf.DB_TYPE))
@task
def reset_conf():
change_conf()
@ -174,7 +201,9 @@ def change_conf(next_conf=None):
signal = "# configurations recommended by ottertune:\n"
next_conf = next_conf or {}
with open(dconf.DB_CONF, 'r') as f:
tmp_conf_in = os.path.join(dconf.TEMP_DIR, os.path.basename(dconf.DB_CONF) + '.in')
get(dconf.DB_CONF, tmp_conf_in)
with open(tmp_conf_in, 'r') as f:
lines = f.readlines()
if signal not in lines:
@ -183,7 +212,8 @@ def change_conf(next_conf=None):
signal_idx = lines.index(signal)
lines = lines[0:signal_idx + 1]
if dconf.BASE_DB_CONF:
assert isinstance(dconf.BASE_DB_CONF, dict)
assert isinstance(dconf.BASE_DB_CONF, dict), \
(type(dconf.BASE_DB_CONF), dconf.BASE_DB_CONF)
base_conf = ['{} = {}\n'.format(*c) for c in sorted(dconf.BASE_DB_CONF.items())]
lines.extend(base_conf)
@ -202,12 +232,13 @@ def change_conf(next_conf=None):
lines.append('{} = {}\n'.format(name, value))
lines.append('\n')
tmpconf = 'tmp_' + os.path.basename(dconf.DB_CONF)
with open(tmpconf, 'w') as f:
tmp_conf_out = os.path.join(dconf.TEMP_DIR, os.path.basename(dconf.DB_CONF) + '.out')
with open(tmp_conf_out, 'w') as f:
f.write(''.join(lines))
local('sudo cp {0} {0}.ottertune.bak'.format(dconf.DB_CONF))
local('sudo mv {} {}'.format(tmpconf, dconf.DB_CONF))
run('cp {0} {0}.ottertune.bak'.format(dconf.DB_CONF))
put(tmp_conf_out, dconf.DB_CONF, use_sudo=False)
local('rm -f {} {}'.format(tmp_conf_in, tmp_conf_out))
@task
@ -249,7 +280,7 @@ def signal_controller():
pidfile = os.path.join(dconf.CONTROLLER_HOME, 'pid.txt')
with open(pidfile, 'r') as f:
pid = int(f.read())
cmd = 'sudo kill -2 {}'.format(pid)
cmd = 'kill -2 {}'.format(pid)
with lcd(dconf.CONTROLLER_HOME): # pylint: disable=not-context-manager
local(cmd)
@ -276,8 +307,11 @@ def save_next_config(next_config, t=None):
@task
def free_cache():
cmd = 'sync; sudo bash -c "echo 1 > /proc/sys/vm/drop_caches"'
local(cmd)
if dconf.HOST_CONN != 'docker': # Read-only file system
with show('everything'), settings(warn_only=True):
res = sudo("sync && echo 3 | tee /proc/sys/vm/drop_caches")
if res.failed:
LOG.error('%s (return code %s)', res.stderr.strip(), res.return_code)
@task
@ -369,7 +403,7 @@ def get_result(max_time_sec=180, interval_sec=5, upload_code=None):
@task
def download_debug_info(pprint=False):
pprint = _parse_bool(pprint)
pprint = parse_bool(pprint)
url = '{}/dump/{}'.format(dconf.WEBSITE_URL, dconf.UPLOAD_CODE)
params = {'pp': int(True)} if pprint else {}
rsp = requests.get(url, params=params)
@ -391,14 +425,13 @@ def download_debug_info(pprint=False):
@task
def add_udf():
cmd = 'sudo python3 ./LatencyUDF.py ../controller/output/'
local(cmd)
local('python3 ./LatencyUDF.py ../controller/output/')
@task
def upload_batch(result_dir=None, sort=True, upload_code=None):
result_dir = result_dir or dconf.RESULT_DIR
sort = _parse_bool(sort)
sort = parse_bool(sort)
results = glob.glob(os.path.join(result_dir, '*__summary.json'))
if sort:
results = sorted(results)
@ -415,44 +448,50 @@ def upload_batch(result_dir=None, sort=True, upload_code=None):
@task
def dump_database():
db_file_path = os.path.join(dconf.DB_DUMP_DIR, dconf.DB_NAME + '.dump')
if os.path.exists(db_file_path):
LOG.info('%s already exists ! ', db_file_path)
dumpfile = os.path.join(dconf.DB_DUMP_DIR, dconf.DB_NAME + '.dump')
if file_exists(dumpfile):
LOG.info('%s already exists ! ', dumpfile)
return False
else:
LOG.info('Dump database %s to %s', dconf.DB_NAME, db_file_path)
# You must create a directory named dpdata through sqlplus in your Oracle database
LOG.info('Dump database %s to %s', dconf.DB_NAME, dumpfile)
if dconf.DB_TYPE == 'oracle':
cmd = 'expdp {}/{}@{} schemas={} dumpfile={}.dump DIRECTORY=dpdata'.format(
'c##tpcc', 'oracle', 'orcldb', 'c##tpcc', 'orcldb')
cmd = 'sh oracleScripts/dumpOracle.sh {} {} {} {}'.format(
dconf.DB_USER, dconf.DB_PASSWORD, dconf.DB_NAME, dconf.DB_DUMP_DIR)
elif dconf.DB_TYPE == 'postgres':
cmd = 'PGPASSWORD={} pg_dump -U {} -F c -d {} > {}'.format(dconf.DB_PASSWORD,
dconf.DB_USER,
dconf.DB_NAME,
db_file_path)
cmd = 'PGPASSWORD={} pg_dump -U {} -h {} -F c -d {} > {}'.format(
dconf.DB_PASSWORD, dconf.DB_USER, dconf.DB_HOST, dconf.DB_NAME,
dumpfile)
else:
raise Exception("Database Type {} Not Implemented !".format(dconf.DB_TYPE))
local(cmd)
run(cmd)
return True
@task
def restore_database():
dumpfile = os.path.join(dconf.DB_DUMP_DIR, dconf.DB_NAME + '.dump')
if not file_exists(dumpfile):
raise FileNotFoundError("Database dumpfile '{}' does not exist!".format(dumpfile))
if dconf.DB_TYPE == 'oracle':
# You must create a directory named dpdata through sqlplus in your Oracle database
# The following script assumes such directory exists.
# You may want to modify the username, password, and dump file name in the script
cmd = 'sh oracleScripts/restoreOracle.sh'
drop_user()
create_user()
cmd = 'sh oracleScripts/restoreOracle.sh {} {}'.format(dconf.DB_USER, dconf.DB_NAME)
elif dconf.DB_TYPE == 'postgres':
db_file_path = '{}/{}.dump'.format(dconf.DB_DUMP_DIR, dconf.DB_NAME)
drop_database()
create_database()
cmd = 'PGPASSWORD={} pg_restore -U {} -n public -j 8 -F c -d {} {}'.\
format(dconf.DB_PASSWORD, dconf.DB_USER, dconf.DB_NAME, db_file_path)
cmd = 'PGPASSWORD={} pg_restore -U {} -h {} -n public -j 8 -F c -d {} {}'.format(
dconf.DB_PASSWORD, dconf.DB_USER, dconf.DB_HOST, dconf.DB_NAME, dumpfile)
else:
raise Exception("Database Type {} Not Implemented !".format(dconf.DB_TYPE))
LOG.info('Start restoring database')
local(cmd)
run(cmd)
LOG.info('Finish restoring database')
@ -485,17 +524,14 @@ def _ready_to_shut_down_controller():
def clean_logs():
# remove oltpbench log
cmd = 'rm -f {}'.format(dconf.OLTPBENCH_LOG)
local(cmd)
# remove controller log
cmd = 'rm -f {}'.format(dconf.CONTROLLER_LOG)
local(cmd)
# remove oltpbench and controller log files
local('rm -f {} {}'.format(dconf.OLTPBENCH_LOG, dconf.CONTROLLER_LOG))
@task
def loop(i):
i = int(i)
# free cache
free_cache()
@ -571,7 +607,7 @@ def run_loops(max_iter=1):
restore_database()
LOG.info('The %s-th Loop Starts / Total Loops %s', i + 1, max_iter)
loop(i % dconf.RELOAD_INTERVAL)
loop(i % dconf.RELOAD_INTERVAL if dconf.RELOAD_INTERVAL > 0 else i)
LOG.info('The %s-th Loop Ends / Total Loops %s', i + 1, max_iter)

View File

@ -0,0 +1,17 @@
#!/bin/sh
USERNAME="$1"
PASSWORD="$2"
sqlplus / as sysdba <<EOF
CREATE USER $USERNAME IDENTIFIED BY $PASSWORD;
GRANT EXECUTE ON DBMS_WORKLOAD_CAPTURE TO $USERNAME;
GRANT EXECUTE ON DBMS_WORKLOAD_REPLAY TO $USERNAME;
GRANT CREATE SESSION TO $USERNAME;
GRANT CREATE ANY DIRECTORY TO $USERNAME;
GRANT SELECT_CATALOG_ROLE TO $USERNAME;
GRANT BECOME USER TO $USERNAME;
GRANT CONNECT, RESOURCE, DBA TO $USERNAME;
quit
EOF

View File

@ -0,0 +1,9 @@
#!/bin/sh
USERNAME="$1"
sqlplus / as sysdba <<EOF
drop user $USERNAME cascade;
quit
EOF

View File

@ -0,0 +1,24 @@
#!/bin/sh
USERNAME="$1"
PASSWORD="$2"
DB_NAME="$3"
DP_PATH="$4"
DP_DIR="DPDATA"
DP_FILE="${DB_NAME}.dump"
# Make sure the physical directory exists
mkdir -p "$DP_PATH"
# Make sure the DB directory object exists
sqlplus / as sysdba <<EOF
CREATE OR REPLACE DIRECTORY $DP_DIR AS '$DP_PATH';
quit
EOF
# Export the data
expdp $USERNAME/$PASSWORD@$DB_NAME \
schemas=$USERNAME \
dumpfile=$DP_FILE \
DIRECTORY=$DP_DIR

View File

@ -1,23 +1,19 @@
#!/bin/sh
su - oracle <<EON
oracle #system password
USERNAME="$1"
DP_FILE="${2}.dump"
DP_DIR="DPDATA"
# Import the data
impdp 'userid="/ as sysdba"' \
schemas=$USERNAME \
dumpfile=$DP_FILE \
DIRECTORY=$DP_DIR
# Restart the database
sqlplus / as sysdba <<EOF
drop user c##tpcc cascade;
# username
create user c##tpcc identified by oracle;
# username password
quit
EOF
impdp 'userid="/ as sysdba"' schemas=c##tpcc dumpfile=orcldb.dump DIRECTORY=dpdata
# username database_name db_directory
sqlplus / as sysdba <<EOF #restart the database
shutdown immediate
startup
quit
EOF
exit
EON

View File

@ -1,11 +1,7 @@
#!/bin/sh
su - oracle <<EON
oracle
sqlplus / as sysdba <<EOF
shutdown immediate
exit
EOF
exit
EON

View File

@ -1,11 +1,7 @@
#!/bin/sh
su - oracle <<EON
oracle
sqlplus / as sysdba <<EOF
exec dbms_workload_repository.create_snapshot;
quit
EOF
exit
EON

View File

@ -1,11 +1,7 @@
#!/bin/sh
su - oracle <<EON
oracle
sqlplus / as sysdba <<EOF
startup
quit
EOF
exit
EON

110
client/driver/utils.py Normal file
View File

@ -0,0 +1,110 @@
import importlib
import os
from fabric.api import hide, local, settings, task
from fabric.api import get as _get, put as _put, run as _run, sudo as _sudo
dconf = None
def load_driver_conf():
driver_conf = os.environ.get('DRIVER_CONFIG', 'driver_config')
if driver_conf.endswith('.py'):
driver_conf = driver_conf[:-len('.py')]
dmod = importlib.import_module(driver_conf)
global dconf
if not dconf:
dconf = dmod
return dmod
def parse_bool(value):
if not isinstance(value, bool):
value = str(value).lower() == 'true'
return value
@task
def run(cmd, **kwargs):
try:
if dconf.HOST_CONN == 'remote':
res = _run(cmd, **kwargs)
elif dconf.HOST_CONN == 'local':
res = local(cmd, capture=True, **kwargs)
else: # docker
opts = ''
cmdd = cmd
if cmd.endswith('&'):
cmdd = cmd[:-1].strip()
opts = '-d '
res = local('docker exec {} -ti {} /bin/bash -c "{}"'.format(
opts, dconf.CONTAINER_NAME, cmdd),
capture=True, **kwargs)
except TypeError as e:
err = str(e).strip()
if 'unexpected keyword argument' in err:
offender = err.rsplit(' ', 1)[-1][1:-1]
kwargs.pop(offender)
res = run(cmd, **kwargs)
else:
raise e
return res
@task
def sudo(cmd, user=None, **kwargs):
if dconf.HOST_CONN == 'remote':
res = _sudo(cmd, user=user, **kwargs)
elif dconf.HOST_CONN == 'local':
pre_cmd = 'sudo '
if user:
pre_cmd += '-u {} '.format(user)
res = local(pre_cmd + cmd, capture=True, **kwargs)
else: # docker
user = user or 'root'
opts = '-ti -u {}'.format(user or 'root')
if user == 'root':
opts += ' -w /'
res = local('docker exec {} {} /bin/bash -c "{}"'.format(
opts, dconf.CONTAINER_NAME, cmd), capture=True)
return res
@task
def get(remote_path, local_path, use_sudo=False):
use_sudo = parse_bool(use_sudo)
if dconf.HOST_CONN == 'remote':
res = _get(remote_path, local_path, use_sudo=use_sudo)
elif dconf.HOST_CONN == 'local':
pre_cmd = 'sudo ' if use_sudo else ''
opts = '-r' if os.path.isdir(remote_path) else ''
res = local('{}cp {} {} {}'.format(pre_cmd, opts, remote_path, local_path))
else: # docker
res = local('docker cp {}:{} {}'.format(dconf.CONTAINER_NAME, remote_path, local_path))
return res
@task
def put(local_path, remote_path, use_sudo=False):
use_sudo = parse_bool(use_sudo)
if dconf.HOST_CONN == 'remote':
res = _put(local_path, remote_path, use_sudo=use_sudo)
elif dconf.HOST_CONN == 'local':
pre_cmd = 'sudo ' if use_sudo else ''
opts = '-r' if os.path.isdir(local_path) else ''
res = local('{}cp {} {} {}'.format(pre_cmd, opts, local_path, remote_path))
else: # docker
res = local('docker cp {} {}:{}'.format(local_path, dconf.CONTAINER_NAME, remote_path))
return res
@task
def file_exists(filename):
with settings(warn_only=True), hide('warnings'):
res = run('[ -f {} ]'.format(filename))
return res.return_code == 0

View File

@ -6,7 +6,7 @@ local_settings.py
# celery/celerybeat #
#####################
celerybeat-schedule
celerybeat-schedule*
*.pid
# Raw data files #