mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-01-18 20:11:20 +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'))
|
sys.path.insert(0, os.path.join(wa_dir, 'framework'))
|
||||||
from version import get_wa_version, get_wa_version_with_commit
|
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: 'install_requires'")
|
||||||
warnings.filterwarnings('ignore', "Unknown distribution option: 'extras_require'")
|
warnings.filterwarnings('ignore', "Unknown distribution option: 'extras_require'")
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ except OSError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
packages = []
|
packages = []
|
||||||
data_files = {}
|
data_files = {'': [os.path.join(wa_dir, 'commands', 'postgres_schema.sql')]}
|
||||||
source_dir = os.path.dirname(__file__)
|
source_dir = os.path.dirname(__file__)
|
||||||
for root, dirs, files in os.walk(wa_dir):
|
for root, dirs, files in os.walk(wa_dir):
|
||||||
rel_dir = os.path.relpath(root, source_dir)
|
rel_dir = os.path.relpath(root, source_dir)
|
||||||
@ -67,6 +67,7 @@ params = dict(
|
|||||||
version=get_wa_version_with_commit(),
|
version=get_wa_version_with_commit(),
|
||||||
packages=packages,
|
packages=packages,
|
||||||
package_data=data_files,
|
package_data=data_files,
|
||||||
|
include_package_data=True,
|
||||||
scripts=scripts,
|
scripts=scripts,
|
||||||
url='https://github.com/ARM-software/workload-automation',
|
url='https://github.com/ARM-software/workload-automation',
|
||||||
license='Apache v2',
|
license='Apache v2',
|
||||||
|
@ -13,16 +13,26 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import stat
|
import stat
|
||||||
import shutil
|
import shutil
|
||||||
import string
|
import string
|
||||||
|
import re
|
||||||
|
import uuid
|
||||||
import getpass
|
import getpass
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from distutils.dir_util import copy_tree # pylint: disable=no-name-in-module, import-error
|
from distutils.dir_util import copy_tree # pylint: disable=no-name-in-module, import-error
|
||||||
|
|
||||||
from devlib.utils.types import identifier
|
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 import ComplexCommand, SubCommand, pluginloader, settings
|
||||||
from wa.framework.target.descriptor import list_target_descriptions
|
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')
|
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):
|
class CreateAgendaSubcommand(SubCommand):
|
||||||
|
|
||||||
name = 'agenda'
|
name = 'agenda'
|
||||||
@ -181,6 +327,7 @@ class CreateCommand(ComplexCommand):
|
|||||||
object-specific arguments.
|
object-specific arguments.
|
||||||
'''
|
'''
|
||||||
subcmd_classes = [
|
subcmd_classes = [
|
||||||
|
CreateDatabaseSubcommand,
|
||||||
CreateWorkloadSubcommand,
|
CreateWorkloadSubcommand,
|
||||||
CreateAgendaSubcommand,
|
CreateAgendaSubcommand,
|
||||||
CreatePackageSubcommand,
|
CreatePackageSubcommand,
|
||||||
@ -280,3 +427,62 @@ def get_class_name(name, postfix=''):
|
|||||||
def touch(path):
|
def touch(path):
|
||||||
with open(path, 'w') as _: # NOQA
|
with open(path, 'w') as _: # NOQA
|
||||||
pass
|
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