Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse config.xml for db parameters, add omero db dump #74

Merged
merged 11 commits into from
Feb 9, 2016
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ before_script:
- psql -c "select 1;" -U omero -h localhost omero
- mkdir $HOME/OMERO
- echo "config set omero.data.dir $HOME/OMERO" > $HOME/config.omero
- echo "config set omero.db.name omero" >> $HOME/config.omero
script:
- sh travis-build

Expand Down
94 changes: 77 additions & 17 deletions omego/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import fileutils
from external import External, RunException
from yaclifw.framework import Command, Stop
from env import EnvDefault, DbParser
from env import DbParser

log = logging.getLogger("omego.db")

Expand All @@ -30,17 +30,15 @@ def __init__(self, dir, command, args, external):
if not os.path.exists(dir):
raise Exception("%s does not exist!" % dir)

self.external = external

psqlv = self.psql('--version')
log.info('psql version: %s', psqlv)

self.external = external

self.check_connection()

if command == 'init':
self.initialise()
elif command == 'upgrade':
self.upgrade()
if command in ('init', 'upgrade', 'dump'):
getattr(self, command)()
else:
raise Stop('Invalid db command: %s', command)

Expand All @@ -51,7 +49,7 @@ def check_connection(self):
log.error(e)
raise Stop(30, 'Database connection check failed')

def initialise(self):
def init(self):
omerosql = self.args.omerosql
autoupgrade = False
if not omerosql:
Expand Down Expand Up @@ -161,23 +159,84 @@ def get_current_db_version(self):
log.info('Current omero db version: %s', v)
return v

def psql(self, *psqlargs):
def dump(self):
"""
Run a psql command
Dump the database using the postgres custom format
"""
if not self.args.dbname:
dumpfile = self.args.dumpfile
if not dumpfile:
db, env = self.get_db_args_env()
dumpfile = fileutils.timestamp_filename(
'omero-database-%s' % db['name'], 'pgdump')

log.info('Dumping database to %s', dumpfile)
if not self.args.dry_run:
self.pgdump('-Fc', '-f', dumpfile)

def get_db_args_env(self):
"""
Get a dictionary of database connection parameters, and create an
environment for running postgres commands.
Falls back to omego defaults.
"""
db = {
'name': self.args.dbname,
'host': self.args.dbhost,
'user': self.args.dbuser,
'pass': self.args.dbpass
}

if not self.args.no_db_config:
try:
c = self.external.get_config(force=True)
except Exception as e:
log.warn('config.xml not found: %s', e)
c = {}

for k in db:
try:
db[k] = c['omero.db.%s' % k]
except KeyError:
log.info(
'Failed to lookup parameter omero.db.%s, using %s',
k, db[k])

if not db['name']:
raise Exception('Database name required')

env = os.environ.copy()
env['PGPASSWORD'] = self.args.dbpass
args = ['-d', self.args.dbname, '-h', self.args.dbhost, '-U',
self.args.dbuser, '-w', '-A', '-t'] + list(psqlargs)
env['PGPASSWORD'] = db['pass']
return db, env

def psql(self, *psqlargs):
"""
Run a psql command
"""
db, env = self.get_db_args_env()

args = ['-d', db['name'], '-h', db['host'], '-U', db['user'],
'-w', '-A', '-t'] + list(psqlargs)
stdout, stderr = External.run('psql', args, capturestd=True, env=env)
if stderr:
log.warn('stderr: %s', stderr)
log.debug('stdout: %s', stdout)
return stdout

def pgdump(self, *pgdumpargs):
"""
Run a pg_dump command
"""
db, env = self.get_db_args_env()

args = ['-d', db['name'], '-h', db['host'], '-U', db['user'], '-w'
] + list(pgdumpargs)
stdout, stderr = External.run(
'pg_dump', args, capturestd=True, env=env)
if stderr:
log.warn('stderr: %s', stderr)
log.debug('stdout: %s', stdout)
return stdout


class DbCommand(Command):
"""
Expand All @@ -192,13 +251,14 @@ def __init__(self, sub_parsers):
self.parser = DbParser(self.parser)
self.parser.add_argument("-n", "--dry-run", action="store_true")

Add = EnvDefault.add
# TODO: Kind of duplicates Upgrade args.sym/args.server
Add(self.parser, 'serverdir', 'Root directory of the server')
self.parser.add_argument(
'--serverdir', help='Root directory of the server')
self.parser.add_argument(
"dbcommand",
choices=['init', 'upgrade'],
choices=['init', 'upgrade', 'dump'],
help='Initialise or upgrade a database')
self.parser.add_argument('--dumpfile', help='Database dump file')

def __call__(self, args):
super(DbCommand, self).__call__(args)
Expand Down
3 changes: 3 additions & 0 deletions omego/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ def __init__(self, parser):
help="Username for connecting to the OMERO database")
Add(group, "dbpass", "omero",
help="Password for connecting to the OMERO database")
group.add_argument(
"--no-db-config", action="store_true",
help="Ignore the database settings in omero config")
# TODO Admin credentials: dbauser, dbapass

Add(group, "omerosql", None,
Expand Down
35 changes: 34 additions & 1 deletion omego/external.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def __init__(self, dir=None):
if dir:
self.set_server_dir(dir)

self._omero = None

def set_server_dir(self, dir):
"""
Set the directory of the server to be controlled
Expand All @@ -68,13 +70,43 @@ def has_config(self):
raise Exception('No server directory set')
return self.configured

def get_config(self, force=False):
"""
Returns a dictionary of all config.xml properties

If `force = True` then ignore any cached state and read config.xml
if possible

setup_omero_cli() must be called before this method to import the
correct omero module to minimise the possibility of version conflicts
"""
if not force and not self.has_config():
raise Exception('No config file')

configxml = os.path.join(self.dir, 'etc', 'grid', 'config.xml')
if not os.path.exists(configxml):
raise Exception('No config file')

try:
# Attempt to open config.xml read-only, though this flag is not
# present in early versions of OMERO 5.0
c = self._omero.config.ConfigXml(
configxml, exclusive=False, read_only=True)
except TypeError:
c = self._omero.config.ConfigXml(configxml, exclusive=False)

try:
return c.as_map()
finally:
c.close()

def setup_omero_cli(self):
"""
Imports the omero CLI module so that commands can be run directly.
Note Python does not allow a module to be imported multiple times,
so this will only work with a single omero instance.

This can have several surprisingly effects, so setup_omero_cli()
This can have several surprising effects, so setup_omero_cli()
must be explcitly called.
"""
if not self.dir:
Expand All @@ -96,6 +128,7 @@ def setup_omero_cli(self):

self.cli = omero.cli.CLI()
self.cli.loadplugins()
self._omero = omero

def setup_previous_omero_env(self, olddir, savevarsfile):
"""
Expand Down
117 changes: 100 additions & 17 deletions test/unit/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def test_check_connection(self, connected):

@pytest.mark.parametrize('sqlfile', ['exists', 'missing', 'notprovided'])
@pytest.mark.parametrize('dryrun', [True, False])
def test_initialise(self, sqlfile, dryrun):
def test_init(self, sqlfile, dryrun):
ext = self.mox.CreateMock(External)
if sqlfile != 'notprovided':
omerosql = 'omero.sql'
Expand Down Expand Up @@ -109,10 +109,10 @@ def test_initialise(self, sqlfile, dryrun):

if sqlfile == 'missing':
with pytest.raises(Stop) as excinfo:
db.initialise()
db.init()
assert str(excinfo.value) == 'SQL file not found'
else:
db.initialise()
db.init()
self.mox.VerifyAll()

def test_sort_schema(self):
Expand Down Expand Up @@ -203,28 +203,111 @@ def test_get_current_db_version(self):
assert db.get_current_db_version() == ('OMERO4.4', '0')
self.mox.VerifyAll()

@pytest.mark.parametrize('dumpfile', ['test.pgdump', None])
@pytest.mark.parametrize('dryrun', [True, False])
def test_dump(self, dumpfile, dryrun):
args = self.Args({'dry_run': dryrun, 'dumpfile': dumpfile})
db = self.PartialMockDb(args, None)
self.mox.StubOutWithMock(omego.fileutils, 'timestamp_filename')
self.mox.StubOutWithMock(db, 'get_db_args_env')
self.mox.StubOutWithMock(db, 'pgdump')

if not dumpfile:
db.get_db_args_env().AndReturn(self.create_db_test_params())

dumpfile = 'omero-database-name-00000000-000000-000000.pgdump'
omego.fileutils.timestamp_filename(
'omero-database-name', 'pgdump').AndReturn(dumpfile)

if not dryrun:
db.pgdump('-Fc', '-f', dumpfile).AndReturn('')

self.mox.ReplayAll()

db.dump()
self.mox.VerifyAll()

def create_db_test_params(self, prefix=''):
db = {
'name': '%sname' % prefix,
'host': '%shost' % prefix,
'user': '%suser' % prefix,
'pass': '%spass' % prefix,
}
env = {'PGPASSWORD': '%spass' % prefix}
return db, env

@pytest.mark.parametrize('dbname', ['name', ''])
def test_psql(self, dbname):
@pytest.mark.parametrize('hasconfig', [True, False])
@pytest.mark.parametrize('noconfig', [True, False])
def test_get_db_args_env(self, dbname, hasconfig, noconfig):
ext = self.mox.CreateMock(External)
args = self.Args({'dbhost': 'host', 'dbname': dbname,
'dbuser': 'user', 'dbpass': 'pass'})

'dbuser': 'user', 'dbpass': 'pass',
'no_db_config': noconfig})
db = self.PartialMockDb(args, ext)
self.mox.StubOutWithMock(db.external, 'has_config')
self.mox.StubOutWithMock(db.external, 'get_config')
self.mox.StubOutWithMock(os.environ, 'copy')
self.mox.StubOutWithMock(External, 'run')

if dbname:
os.environ.copy().AndReturn({'PGPASSWORD': 'incorrect'})
psqlargs = ['-d', dbname, '-h', 'host', '-U', 'user',
'-w', '-A', '-t', 'arg1', 'arg2']
External.run('psql', psqlargs, capturestd=True,
env={'PGPASSWORD': 'pass'}).AndReturn(('', ''))
self.mox.ReplayAll()
if noconfig or not hasconfig:
expecteddb, expectedenv = self.create_db_test_params()
else:
expecteddb, expectedenv = self.create_db_test_params('ext')

db = self.PartialMockDb(args, None)
if not noconfig:
cfg = {}
if hasconfig:
cfg = {
'omero.db.host': 'exthost',
'omero.db.user': 'extuser',
'omero.db.pass': 'extpass',
}
if dbname:
cfg['omero.db.name'] = 'extname'

db.external.get_config(force=True).AndReturn(cfg)
else:
db.external.get_config().AndRaise(Exception())

os.environ.copy().AndReturn({'PGPASSWORD': 'incorrect'})

self.mox.ReplayAll()
if dbname:
db.psql('arg1', 'arg2')
rcfg, renv = db.get_db_args_env()
assert rcfg == expecteddb
assert renv == expectedenv
else:
with pytest.raises(Exception) as excinfo:
db.psql('arg1', 'arg2')
db.get_db_args_env()
assert str(excinfo.value) == 'Database name required'

def test_psql(self):
db = self.PartialMockDb(None, None)
self.mox.StubOutWithMock(db, 'get_db_args_env')
self.mox.StubOutWithMock(External, 'run')

psqlargs = ['-d', 'name', '-h', 'host', '-U', 'user',
'-w', '-A', '-t', 'arg1', 'arg2']
db.get_db_args_env().AndReturn(self.create_db_test_params())
External.run('psql', psqlargs, capturestd=True,
env={'PGPASSWORD': 'pass'}).AndReturn(('', ''))
self.mox.ReplayAll()

db.psql('arg1', 'arg2')
self.mox.VerifyAll()

def test_pgdump(self):
db = self.PartialMockDb(None, None)
self.mox.StubOutWithMock(db, 'get_db_args_env')
self.mox.StubOutWithMock(External, 'run')

pgdumpargs = ['-d', 'name', '-h', 'host', '-U', 'user',
'-w', 'arg1', 'arg2']
db.get_db_args_env().AndReturn(self.create_db_test_params())
External.run('pg_dump', pgdumpargs, capturestd=True,
env={'PGPASSWORD': 'pass'}).AndReturn(('', ''))
self.mox.ReplayAll()

db.pgdump('arg1', 'arg2')
self.mox.VerifyAll()
3 changes: 3 additions & 0 deletions test/unit/test_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ def test_set_server_dir_and_has_config(self, tmpdir, configured):
tmpdir.ensure('etc', 'grid', 'config.xml')
assert self.ext.has_config() == configured

# def test_get_config(self):
# Not easily testable since it requires the omero module

# def test_setup_omero_cli(self):
# Not easily testable since it does a direct import

Expand Down
Loading