from alembic import command as alembic_command
from alembic import config as alembic_config
+from alembic import script as alembic_script
from alembic import util as alembic_util
from oslo.config import cfg
from neutron.common import legacy
+HEAD_FILENAME = 'HEAD'
+
_core_opts = [
cfg.StrOpt('core_plugin',
def do_check_migration(config, cmd):
do_alembic_command(config, 'branches')
+ validate_head_file(config)
def do_upgrade_downgrade(config, cmd):
message=CONF.command.message,
autogenerate=CONF.command.autogenerate,
sql=CONF.command.sql)
+ update_head_file(config)
+
+
+def validate_head_file(config):
+ script = alembic_script.ScriptDirectory.from_config(config)
+ if len(script.get_heads()) > 1:
+ alembic_util.err(_('Timeline branches unable to generate timeline'))
+
+ head_path = os.path.join(script.versions, HEAD_FILENAME)
+ if (os.path.isfile(head_path) and
+ open(head_path).read().strip() == script.get_current_head()):
+ return
+ else:
+ alembic_util.err(_('HEAD file does not match migration timeline head'))
+
+
+def update_head_file(config):
+ script = alembic_script.ScriptDirectory.from_config(config)
+ if len(script.get_heads()) > 1:
+ alembic_util.err(_('Timeline branches unable to generate timeline'))
+
+ head_path = os.path.join(script.versions, HEAD_FILENAME)
+ with open(head_path, 'w+') as f:
+ f.write(script.get_current_head())
def add_command_parsers(subparsers):
super(TestCli, self).setUp()
self.do_alembic_cmd_p = mock.patch.object(cli, 'do_alembic_command')
self.do_alembic_cmd = self.do_alembic_cmd_p.start()
+ self.mock_alembic_err = mock.patch('alembic.util.err').start()
+ self.mock_alembic_err.side_effect = SystemExit
self.addCleanup(self.do_alembic_cmd_p.stop)
self.addCleanup(cli.CONF.reset)
self._main_test_helper(['prog', 'history'], 'history')
def test_check_migration(self):
- self._main_test_helper(['prog', 'check_migration'], 'branches')
+ with mock.patch.object(cli, 'validate_head_file') as validate:
+ self._main_test_helper(['prog', 'check_migration'], 'branches')
+ validate.assert_called_once_with(mock.ANY)
def test_database_sync_revision(self):
- self._main_test_helper(
- ['prog', 'revision', '--autogenerate', '-m', 'message'],
- 'revision',
- (),
- {'message': 'message', 'sql': False, 'autogenerate': True}
- )
-
- self._main_test_helper(
- ['prog', 'revision', '--sql', '-m', 'message'],
- 'revision',
- (),
- {'message': 'message', 'sql': True, 'autogenerate': False}
- )
+ with mock.patch.object(cli, 'update_head_file') as update:
+ self._main_test_helper(
+ ['prog', 'revision', '--autogenerate', '-m', 'message'],
+ 'revision',
+ (),
+ {'message': 'message', 'sql': False, 'autogenerate': True}
+ )
+ update.assert_called_once_with(mock.ANY)
+
+ update.reset_mock()
+ self._main_test_helper(
+ ['prog', 'revision', '--sql', '-m', 'message'],
+ 'revision',
+ (),
+ {'message': 'message', 'sql': True, 'autogenerate': False}
+ )
+ update.assert_called_once_with(mock.ANY)
def test_upgrade(self):
self._main_test_helper(
('-2',),
{'sql': False}
)
+
+ def _test_validate_head_file_helper(self, heads, file_content=None):
+ with mock.patch('alembic.script.ScriptDirectory.from_config') as fc:
+ fc.return_value.get_heads.return_value = heads
+ fc.return_value.get_current_head.return_value = heads[0]
+ with mock.patch('__builtin__.open') as mock_open:
+ mock_open.return_value.__enter__ = lambda s: s
+ mock_open.return_value.__exit__ = mock.Mock()
+ mock_open.return_value.read.return_value = file_content
+
+ with mock.patch('os.path.isfile') as is_file:
+ is_file.return_value = file_content is not None
+
+ if file_content in heads:
+ cli.validate_head_file(mock.sentinel.config)
+ else:
+ self.assertRaises(
+ SystemExit,
+ cli.validate_head_file,
+ mock.sentinel.config
+ )
+ self.mock_alembic_err.assert_called_once_with(mock.ANY)
+ fc.assert_called_once_with(mock.sentinel.config)
+
+ def test_validate_head_file_multiple_heads(self):
+ self._test_validate_head_file_helper(['a', 'b'])
+
+ def test_validate_head_file_missing_file(self):
+ self._test_validate_head_file_helper(['a'])
+
+ def test_validate_head_file_wrong_contents(self):
+ self._test_validate_head_file_helper(['a'], 'b')
+
+ def test_validate_head_success(self):
+ self._test_validate_head_file_helper(['a'], 'a')
+
+ def test_update_head_file_multiple_heads(self):
+ with mock.patch('alembic.script.ScriptDirectory.from_config') as fc:
+ fc.return_value.get_heads.return_value = ['a', 'b']
+ self.assertRaises(
+ SystemExit,
+ cli.update_head_file,
+ mock.sentinel.config
+ )
+ self.mock_alembic_err.assert_called_once_with(mock.ANY)
+ fc.assert_called_once_with(mock.sentinel.config)
+
+ def test_update_head_file_success(self):
+ with mock.patch('alembic.script.ScriptDirectory.from_config') as fc:
+ fc.return_value.get_heads.return_value = ['a']
+ fc.return_value.get_current_head.return_value = 'a'
+ with mock.patch('__builtin__.open') as mock_open:
+ mock_open.return_value.__enter__ = lambda s: s
+ mock_open.return_value.__exit__ = mock.Mock()
+
+ cli.update_head_file(mock.sentinel.config)
+ mock_open.write.called_once_with('a')
+ fc.assert_called_once_with(mock.sentinel.config)