mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-01-18 12:06:08 +00:00
Add WA Create Database Command
Add a command to create a PostgreSQL database with supplied parameters which draws its structure from the supplied schema (Version 1.1). This database is of a format intended to be used with the forthcoming WA Postgres output processor.
This commit is contained in:
parent
ca03f21f46
commit
bb255de9ad
5
setup.py
Normal file → Executable file
5
setup.py
Normal file → Executable file
@ -31,7 +31,7 @@ wa_dir = os.path.join(os.path.dirname(__file__), 'wa')
|
||||
sys.path.insert(0, os.path.join(wa_dir, 'framework'))
|
||||
from version import get_wa_version, get_wa_version_with_commit
|
||||
|
||||
# happends if falling back to distutils
|
||||
# happens if falling back to distutils
|
||||
warnings.filterwarnings('ignore', "Unknown distribution option: 'install_requires'")
|
||||
warnings.filterwarnings('ignore', "Unknown distribution option: 'extras_require'")
|
||||
|
||||
@ -41,7 +41,7 @@ except OSError:
|
||||
pass
|
||||
|
||||
packages = []
|
||||
data_files = {}
|
||||
data_files = {'': [os.path.join(wa_dir, 'commands', 'postgres_schema.sql')]}
|
||||
source_dir = os.path.dirname(__file__)
|
||||
for root, dirs, files in os.walk(wa_dir):
|
||||
rel_dir = os.path.relpath(root, source_dir)
|
||||
@ -67,6 +67,7 @@ params = dict(
|
||||
version=get_wa_version_with_commit(),
|
||||
packages=packages,
|
||||
package_data=data_files,
|
||||
include_package_data=True,
|
||||
scripts=scripts,
|
||||
url='https://github.com/ARM-software/workload-automation',
|
||||
license='Apache v2',
|
||||
|
@ -13,16 +13,26 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
import shutil
|
||||
import string
|
||||
import re
|
||||
import uuid
|
||||
import getpass
|
||||
from collections import OrderedDict
|
||||
from distutils.dir_util import copy_tree # pylint: disable=no-name-in-module, import-error
|
||||
|
||||
from devlib.utils.types import identifier
|
||||
try:
|
||||
import psycopg2
|
||||
from psycopg2 import connect, OperationalError, extras
|
||||
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
|
||||
except ImportError as e:
|
||||
psycopg2 = None
|
||||
import_error_msg = e.args[0] if e.args else str(e)
|
||||
|
||||
from wa import ComplexCommand, SubCommand, pluginloader, settings
|
||||
from wa.framework.target.descriptor import list_target_descriptions
|
||||
@ -36,6 +46,142 @@ from wa.utils.serializer import yaml
|
||||
TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), 'templates')
|
||||
|
||||
|
||||
class CreateDatabaseSubcommand(SubCommand):
|
||||
|
||||
name = 'database'
|
||||
description = """
|
||||
Create a Postgresql database which is compatible with the WA Postgres
|
||||
output processor.
|
||||
"""
|
||||
|
||||
schemafilepath = 'postgres_schema.sql'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateDatabaseSubcommand, self).__init__(*args, **kwargs)
|
||||
self.sql_commands = None
|
||||
self.schemaversion = None
|
||||
self.schema_major = None
|
||||
self.schema_minor = None
|
||||
|
||||
def initialize(self, context):
|
||||
self.parser.add_argument(
|
||||
'-a', '--postgres-host', default='localhost',
|
||||
help='The host on which to create the database.')
|
||||
self.parser.add_argument(
|
||||
'-k', '--postgres-port', default='5432',
|
||||
help='The port on which the PostgreSQL server is running.')
|
||||
self.parser.add_argument(
|
||||
'-u', '--username', default='postgres',
|
||||
help='The username with which to connect to the server.')
|
||||
self.parser.add_argument(
|
||||
'-p', '--password',
|
||||
help='The password for the user account.')
|
||||
self.parser.add_argument(
|
||||
'-d', '--dbname', default='wa',
|
||||
help='The name of the database to create.')
|
||||
self.parser.add_argument(
|
||||
'-f', '--force', action='store_true',
|
||||
help='Force overwrite the existing database if one exists.')
|
||||
self.parser.add_argument(
|
||||
'-F', '--force-update-config', action='store_true',
|
||||
help='Force update the config file if an entry exists.')
|
||||
self.parser.add_argument(
|
||||
'-r', '--config-file', default=settings.user_config_file,
|
||||
help='Path to the config file to be updated.')
|
||||
self.parser.add_argument(
|
||||
'-x', '--schema-version', action='store_true',
|
||||
help='Display the current schema version.')
|
||||
|
||||
def execute(self, state, args): # pylint: disable=too-many-branches
|
||||
if not psycopg2:
|
||||
raise CommandError(
|
||||
'The module psycopg2 is required for the wa ' +
|
||||
'create database command.')
|
||||
self.get_schema(self.schemafilepath)
|
||||
|
||||
# Display the version if needed and exit
|
||||
if args.schema_version:
|
||||
self.logger.info(
|
||||
'The current schema version is {}'.format(self.schemaversion))
|
||||
return
|
||||
|
||||
if args.dbname == 'postgres':
|
||||
raise ValueError('Databasename to create cannot be postgres.')
|
||||
|
||||
# Open user configuration
|
||||
with open(args.config_file, 'r') as config_file:
|
||||
config = yaml.load(config_file)
|
||||
if 'postgres' in config and not args.force_update_config:
|
||||
raise CommandError(
|
||||
"The entry 'postgres' already exists in the config file. " +
|
||||
"Please specify the -F flag to force an update.")
|
||||
|
||||
possible_connection_errors = [
|
||||
(
|
||||
re.compile('FATAL: role ".*" does not exist'),
|
||||
'Username does not exist or password is incorrect'
|
||||
),
|
||||
(
|
||||
re.compile('FATAL: password authentication failed for user'),
|
||||
'Password was incorrect'
|
||||
),
|
||||
(
|
||||
re.compile('fe_sendauth: no password supplied'),
|
||||
'Passwordless connection is not enabled. '
|
||||
'Please enable trust in pg_hba for this host '
|
||||
'or use a password'
|
||||
),
|
||||
(
|
||||
re.compile('FATAL: no pg_hba.conf entry for'),
|
||||
'Host is not allowed to connect to the specified database '
|
||||
'using this user according to pg_hba.conf. Please change the '
|
||||
'rules in pg_hba or your connection method'
|
||||
),
|
||||
(
|
||||
re.compile('FATAL: pg_hba.conf rejects connection'),
|
||||
'Connection was rejected by pg_hba.conf'
|
||||
),
|
||||
]
|
||||
|
||||
def predicate(error, handle):
|
||||
if handle[0].match(str(error)):
|
||||
raise CommandError(handle[1] + ': \n' + str(error))
|
||||
|
||||
# Attempt to create database
|
||||
try:
|
||||
self.create_database(args)
|
||||
except OperationalError as e:
|
||||
for handle in possible_connection_errors:
|
||||
predicate(e, handle)
|
||||
raise e
|
||||
|
||||
# Update the configuration file
|
||||
_update_configuration_file(args, config)
|
||||
|
||||
def create_database(self, args):
|
||||
_check_database_existence(args)
|
||||
|
||||
_create_database_postgres(args)
|
||||
|
||||
_apply_database_schema(args, self.sql_commands, self.schema_major, self.schema_minor)
|
||||
|
||||
self.logger.debug(
|
||||
"Successfully created the database {}".format(args.dbname))
|
||||
|
||||
def get_schema(self, schemafilepath):
|
||||
postgres_output_processor_dir = os.path.dirname(__file__)
|
||||
sqlfile = open(os.path.join(
|
||||
postgres_output_processor_dir, schemafilepath))
|
||||
self.sql_commands = sqlfile.read()
|
||||
sqlfile.close()
|
||||
# Extract schema version
|
||||
if self.sql_commands.startswith('--!VERSION'):
|
||||
splitcommands = self.sql_commands.split('!ENDVERSION!\n')
|
||||
self.schemaversion = splitcommands[0].strip('--!VERSION!')
|
||||
(self.schema_major, self.schema_minor) = self.schemaversion.split('.')
|
||||
self.sql_commands = splitcommands[1]
|
||||
|
||||
|
||||
class CreateAgendaSubcommand(SubCommand):
|
||||
|
||||
name = 'agenda'
|
||||
@ -181,6 +327,7 @@ class CreateCommand(ComplexCommand):
|
||||
object-specific arguments.
|
||||
'''
|
||||
subcmd_classes = [
|
||||
CreateDatabaseSubcommand,
|
||||
CreateWorkloadSubcommand,
|
||||
CreateAgendaSubcommand,
|
||||
CreatePackageSubcommand,
|
||||
@ -280,3 +427,62 @@ def get_class_name(name, postfix=''):
|
||||
def touch(path):
|
||||
with open(path, 'w') as _: # NOQA
|
||||
pass
|
||||
|
||||
|
||||
def _check_database_existence(args):
|
||||
try:
|
||||
connect(dbname=args.dbname, user=args.username,
|
||||
password=args.password, host=args.postgres_host, port=args.postgres_port)
|
||||
except OperationalError as e:
|
||||
# Expect an operational error (database's non-existence)
|
||||
if not re.compile('FATAL: database ".*" does not exist').match(str(e)):
|
||||
raise e
|
||||
else:
|
||||
if not args.force:
|
||||
raise CommandError(
|
||||
"Database {} already exists. ".format(args.dbname) +
|
||||
"Please specify the -f flag to create it from afresh."
|
||||
)
|
||||
|
||||
|
||||
def _create_database_postgres(args): # pylint: disable=no-self-use
|
||||
conn = connect(dbname='postgres', user=args.username,
|
||||
password=args.password, host=args.postgres_host, port=args.postgres_port)
|
||||
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('DROP DATABASE IF EXISTS ' + args.dbname)
|
||||
cursor.execute('CREATE DATABASE ' + args.dbname)
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
def _apply_database_schema(args, sql_commands, schema_major, schema_minor):
|
||||
conn = connect(dbname=args.dbname, user=args.username,
|
||||
password=args.password, host=args.postgres_host, port=args.postgres_port)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(sql_commands)
|
||||
|
||||
extras.register_uuid()
|
||||
cursor.execute("INSERT INTO DatabaseMeta VALUES (%s, %s, %s)",
|
||||
(
|
||||
uuid.uuid4(),
|
||||
schema_major,
|
||||
schema_minor
|
||||
)
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
def _update_configuration_file(args, config):
|
||||
''' Update the user configuration file with the newly created database's
|
||||
configuration.
|
||||
'''
|
||||
config['postgres'] = OrderedDict(
|
||||
[('host', args.postgres_host), ('port', args.postgres_port),
|
||||
('dbname', args.dbname), ('username', args.username), ('password', args.password)])
|
||||
with open(args.config_file, 'w+') as config_file:
|
||||
yaml.dump(config, config_file)
|
||||
|
170
wa/commands/postgres_schema.sql
Normal file
170
wa/commands/postgres_schema.sql
Normal file
@ -0,0 +1,170 @@
|
||||
--!VERSION!1.1!ENDVERSION!
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "lo";
|
||||
|
||||
-- In future, it may be useful to implement rules on which Parameter oid fields can be none depeendent on the value in the type column;
|
||||
|
||||
DROP TABLE IF EXISTS DatabaseMeta;
|
||||
DROP TABLE IF EXISTS Parameters;
|
||||
DROP TABLE IF EXISTS Classifiers;
|
||||
DROP TABLE IF EXISTS LargeObjects;
|
||||
DROP TABLE IF EXISTS Artifacts;
|
||||
DROP TABLE IF EXISTS Metrics;
|
||||
DROP TABLE IF EXISTS Augmentations;
|
||||
DROP TABLE IF EXISTS Jobs_Augs;
|
||||
DROP TABLE IF EXISTS ResourceGetters;
|
||||
DROP TABLE IF EXISTS Events;
|
||||
DROP TABLE IF EXISTS Targets;
|
||||
DROP TABLE IF EXISTS Jobs;
|
||||
DROP TABLE IF EXISTS Runs;
|
||||
|
||||
DROP TYPE IF EXISTS status_enum;
|
||||
DROP TYPE IF EXISTS param_enum;
|
||||
|
||||
CREATE TYPE status_enum AS ENUM ('UNKNOWN(0)','NEW(1)','PENDING(2)','STARTED(3)','CONNECTED(4)', 'INITIALIZED(5)', 'RUNNING(6)', 'OK(7)', 'PARTIAL(8)', 'FAILED(9)', 'ABORTED(10)', 'SKIPPED(11)');
|
||||
|
||||
CREATE TYPE param_enum AS ENUM ('workload', 'resource_getter', 'augmentation', 'device', 'runtime', 'boot');
|
||||
|
||||
-- In future, it might be useful to create an ENUM type for the artifact kind, or simply a generic enum type;
|
||||
|
||||
CREATE TABLE DatabaseMeta (
|
||||
oid uuid NOT NULL,
|
||||
schema_major int,
|
||||
schema_minor int,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE Runs (
|
||||
oid uuid NOT NULL,
|
||||
event_summary text,
|
||||
basepath text,
|
||||
status status_enum,
|
||||
timestamp timestamp,
|
||||
run_name text,
|
||||
project text,
|
||||
retry_on_status status_enum[],
|
||||
max_retries int,
|
||||
bail_on_init_failure boolean,
|
||||
allow_phone_home boolean,
|
||||
run_uuid uuid,
|
||||
start_time timestamp,
|
||||
end_time timestamp,
|
||||
metadata jsonb,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE Jobs (
|
||||
oid uuid NOT NULL,
|
||||
run_oid uuid NOT NULL references Runs(oid),
|
||||
status status_enum,
|
||||
retries int,
|
||||
label text,
|
||||
job_id text,
|
||||
iterations int,
|
||||
workload_name text,
|
||||
metadata jsonb,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE Targets (
|
||||
oid uuid NOT NULL,
|
||||
run_oid uuid NOT NULL references Runs(oid),
|
||||
target text,
|
||||
cpus text[],
|
||||
os text,
|
||||
os_version jsonb,
|
||||
hostid int,
|
||||
hostname text,
|
||||
abi text,
|
||||
is_rooted boolean,
|
||||
kernel_version text,
|
||||
kernel_release text,
|
||||
kernel_sha1 text,
|
||||
kernel_config text[],
|
||||
sched_features text[],
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE Events (
|
||||
oid uuid NOT NULL,
|
||||
run_oid uuid NOT NULL references Runs(oid),
|
||||
job_oid uuid references Jobs(oid),
|
||||
timestamp timestamp,
|
||||
message text,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE ResourceGetters (
|
||||
oid uuid NOT NULL,
|
||||
run_oid uuid NOT NULL references Runs(oid),
|
||||
name text,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE Augmentations (
|
||||
oid uuid NOT NULL,
|
||||
run_oid uuid NOT NULL references Runs(oid),
|
||||
name text,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE Jobs_Augs (
|
||||
oid uuid NOT NULL,
|
||||
job_oid uuid NOT NULL references Jobs(oid),
|
||||
augmentation_oid uuid NOT NULL references Augmentations(oid),
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE Metrics (
|
||||
oid uuid NOT NULL,
|
||||
run_oid uuid NOT NULL references Runs(oid),
|
||||
job_oid uuid references Jobs(oid),
|
||||
name text,
|
||||
value double precision,
|
||||
units text,
|
||||
lower_is_better boolean,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE LargeObjects (
|
||||
oid uuid NOT NULL,
|
||||
lo_oid lo NOT NULL,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
-- Trigger that allows you to manage large objects from the LO table directly;
|
||||
CREATE TRIGGER t_raster BEFORE UPDATE OR DELETE ON LargeObjects
|
||||
FOR EACH ROW EXECUTE PROCEDURE lo_manage(lo_oid);
|
||||
|
||||
CREATE TABLE Artifacts (
|
||||
oid uuid NOT NULL,
|
||||
run_oid uuid NOT NULL references Runs(oid),
|
||||
job_oid uuid references Jobs(oid),
|
||||
name text,
|
||||
large_object_uuid uuid NOT NULL references LargeObjects(oid),
|
||||
description text,
|
||||
kind text,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE Classifiers (
|
||||
oid uuid NOT NULL,
|
||||
artifact_oid uuid references Artifacts(oid),
|
||||
metric_oid uuid references Metrics(oid),
|
||||
key text,
|
||||
value text,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
||||
|
||||
CREATE TABLE Parameters (
|
||||
oid uuid NOT NULL,
|
||||
run_oid uuid NOT NULL references Runs(oid),
|
||||
job_oid uuid references Jobs(oid),
|
||||
augmentation_oid uuid references Augmentations(oid),
|
||||
resource_getter_oid uuid references ResourceGetters(oid),
|
||||
name text,
|
||||
value text,
|
||||
value_type text,
|
||||
type param_enum,
|
||||
PRIMARY KEY (oid)
|
||||
);
|
9
wa/commands/schema_changelog.rst
Normal file
9
wa/commands/schema_changelog.rst
Normal file
@ -0,0 +1,9 @@
|
||||
# 1
|
||||
## 1.0
|
||||
- First version
|
||||
## 1.1
|
||||
- LargeObjects table added as a substitute for the previous plan to
|
||||
use the filesystem and a path reference to store artifacts. This
|
||||
was done following an extended discussion and tests that verified
|
||||
that the savings in processing power were not enough to warrant
|
||||
the creation of a dedicated server or file handler.
|
Loading…
x
Reference in New Issue
Block a user