[neutron-db-manage] Introduce contract and expand commands

Those are just wrappers that pass proper @head revision into upgrade
alembic API.

Change-Id: I4d7e1bd992c3ad75fca6d72defa9f96565ad6ba9
Partially-Implements: blueprint online-schema-migrations
This commit is contained in:
Ihar Hrachyshka 2015-08-24 17:58:12 +02:00
parent 1c19e898c0
commit bac73541f4
3 changed files with 113 additions and 39 deletions

View File

@ -294,12 +294,12 @@ Applying database migration rules
To apply just expansion rules, execute::
neutron-db-manage upgrade expand@head
neutron-db-manage upgrade --expand
After the first step is done, you can stop neutron-server, apply remaining
non-expansive migration rules, if any::
neutron-db-manage upgrade contract@head
neutron-db-manage upgrade --contract
and finally, start your neutron-server again.

View File

@ -98,10 +98,18 @@ CONF.register_cli_opts(_db_opts, 'database')
CONF.register_opts(_quota_opts, 'QUOTAS')
def do_alembic_command(config, cmd, *args, **kwargs):
def do_alembic_command(config, cmd, revision=None, desc=None, **kwargs):
args = []
if revision:
args.append(revision)
project = config.get_main_option('neutron_project')
alembic_util.msg(_('Running %(cmd)s for %(project)s ...') %
{'cmd': cmd, 'project': project})
if desc:
alembic_util.msg(_('Running %(cmd)s (%(desc)s) for %(project)s ...') %
{'cmd': cmd, 'desc': desc, 'project': project})
else:
alembic_util.msg(_('Running %(cmd)s for %(project)s ...') %
{'cmd': cmd, 'project': project})
try:
getattr(alembic_command, cmd)(config, *args, **kwargs)
except alembic_util.CommandError as e:
@ -126,30 +134,48 @@ def add_alembic_subparser(sub, cmd):
def do_upgrade(config, cmd):
if not CONF.command.revision and not CONF.command.delta:
desc = None
if ((CONF.command.revision or CONF.command.delta) and
(CONF.command.expand or CONF.command.contract)):
raise SystemExit(_(
'Phase upgrade options do not accept revision specification'))
if CONF.command.expand:
desc = EXPAND_BRANCH
revision = _get_branch_head(EXPAND_BRANCH)
elif CONF.command.contract:
desc = CONTRACT_BRANCH
revision = _get_branch_head(CONTRACT_BRANCH)
elif not CONF.command.revision and not CONF.command.delta:
raise SystemExit(_('You must provide a revision or relative delta'))
revision = CONF.command.revision or ''
if '-' in revision:
raise SystemExit(_('Negative relative revision (downgrade) not '
'supported'))
else:
revision = CONF.command.revision or ''
if '-' in revision:
raise SystemExit(_('Negative relative revision (downgrade) not '
'supported'))
delta = CONF.command.delta
if delta:
if '+' in revision:
raise SystemExit(_('Use either --delta or relative revision, '
'not both'))
if delta < 0:
raise SystemExit(_('Negative delta (downgrade) not supported'))
revision = '%s+%d' % (revision, delta)
delta = CONF.command.delta
if delta:
if '+' in revision:
raise SystemExit(_('Use either --delta or relative revision, '
'not both'))
if delta < 0:
raise SystemExit(_('Negative delta (downgrade) not supported'))
revision = '%s+%d' % (revision, delta)
# leave branchless 'head' revision request backward compatible by
# applying all heads in all available branches.
if revision == 'head':
revision = 'heads'
# leave branchless 'head' revision request backward compatible by applying
# all heads in all available branches.
if revision == 'head':
revision = 'heads'
if not CONF.command.sql:
run_sanity_checks(config, revision)
do_alembic_command(config, cmd, revision, sql=CONF.command.sql)
do_alembic_command(config, cmd, revision=revision,
desc=desc, sql=CONF.command.sql)
def no_downgrade(config, cmd):
@ -158,7 +184,7 @@ def no_downgrade(config, cmd):
def do_stamp(config, cmd):
do_alembic_command(config, cmd,
CONF.command.revision,
revision=CONF.command.revision,
sql=CONF.command.sql)
@ -311,6 +337,11 @@ def add_command_parsers(subparsers):
default='',
help='Change MySQL storage engine of current '
'existing tables')
group = parser.add_mutually_exclusive_group()
group.add_argument('--expand', action='store_true')
group.add_argument('--contract', action='store_true')
parser.set_defaults(func=do_upgrade)
parser = subparsers.add_parser('downgrade', help="(No longer supported)")

View File

@ -25,6 +25,7 @@ import pkg_resources
from neutron.db import migration
from neutron.db.migration import cli
from neutron.tests import base
from neutron.tests.unit import testlib_api
class FakeConfig(object):
@ -135,14 +136,14 @@ class TestCli(base.BaseTestCase):
attrs=attrs)
cli.migration_entrypoints[project] = entrypoint
def _main_test_helper(self, argv, func_name, exp_args=(), exp_kwargs=[{}]):
def _main_test_helper(self, argv, func_name, exp_kwargs=[{}]):
with mock.patch.object(sys, 'argv', argv),\
mock.patch.object(cli, 'run_sanity_checks'),\
mock.patch.object(cli, 'validate_labels'):
cli.main()
self.do_alembic_cmd.assert_has_calls(
[mock.call(mock.ANY, func_name, *exp_args, **kwargs)
[mock.call(mock.ANY, func_name, **kwargs)
for kwargs in exp_kwargs]
)
@ -150,15 +151,13 @@ class TestCli(base.BaseTestCase):
self._main_test_helper(
['prog', 'stamp', 'foo'],
'stamp',
('foo',),
[{'sql': False}]
[{'revision': 'foo', 'sql': False}]
)
self._main_test_helper(
['prog', 'stamp', 'foo', '--sql'],
'stamp',
('foo',),
[{'sql': True}]
[{'revision': 'foo', 'sql': True}]
)
def test_current(self):
@ -192,7 +191,7 @@ class TestCli(base.BaseTestCase):
self._main_test_helper(
['prog', 'revision', '--autogenerate', '-m', 'message'],
'revision',
(), expected_kwargs
expected_kwargs
)
self.assertEqual(len(self.projects), update.call_count)
update.reset_mock()
@ -204,7 +203,7 @@ class TestCli(base.BaseTestCase):
self._main_test_helper(
['prog', 'revision', '--sql', '-m', 'message'],
'revision',
(), expected_kwargs
expected_kwargs
)
self.assertEqual(len(self.projects), update.call_count)
@ -215,26 +214,70 @@ class TestCli(base.BaseTestCase):
# Test that old branchless approach is still supported
self._test_database_sync_revision(separate_branches=False)
def test_upgrade(self):
def test_upgrade_revision(self):
self._main_test_helper(
['prog', 'upgrade', '--sql', 'head'],
'upgrade',
('heads',),
[{'sql': True}]
[{'desc': None, 'revision': 'heads', 'sql': True}]
)
def test_upgrade_delta(self):
self._main_test_helper(
['prog', 'upgrade', '--delta', '3'],
'upgrade',
('+3',),
[{'sql': False}]
[{'desc': None, 'revision': '+3', 'sql': False}]
)
def test_upgrade_revision_delta(self):
self._main_test_helper(
['prog', 'upgrade', 'kilo', '--delta', '3'],
'upgrade',
('kilo+3',),
[{'sql': False}]
[{'desc': None, 'revision': 'kilo+3', 'sql': False}]
)
def test_upgrade_expand(self):
self._main_test_helper(
['prog', 'upgrade', '--expand'],
'upgrade',
[{'desc': cli.EXPAND_BRANCH,
'revision': 'expand@head',
'sql': False}]
)
def test_upgrade_expand_contract_are_mutually_exclusive(self):
with testlib_api.ExpectedException(SystemExit):
self._main_test_helper(
['prog', 'upgrade', '--expand --contract'], 'upgrade')
def _test_upgrade_conflicts_with_revision(self, mode):
with testlib_api.ExpectedException(SystemExit):
self._main_test_helper(
['prog', 'upgrade', '--%s revision1' % mode], 'upgrade')
def _test_upgrade_conflicts_with_delta(self, mode):
with testlib_api.ExpectedException(SystemExit):
self._main_test_helper(
['prog', 'upgrade', '--%s +3' % mode], 'upgrade')
def test_upgrade_expand_conflicts_with_revision(self):
self._test_upgrade_conflicts_with_revision('expand')
def test_upgrade_contract_conflicts_with_revision(self):
self._test_upgrade_conflicts_with_revision('contract')
def test_upgrade_expand_conflicts_with_delta(self):
self._test_upgrade_conflicts_with_delta('expand')
def test_upgrade_contract_conflicts_with_delta(self):
self._test_upgrade_conflicts_with_delta('contract')
def test_upgrade_contract(self):
self._main_test_helper(
['prog', 'upgrade', '--contract'],
'upgrade',
[{'desc': cli.CONTRACT_BRANCH,
'revision': 'contract@head',
'sql': False}]
)
def assert_command_fails(self, command):