Merge lp:~nick-moffitt/charms/precise/keystone/migrate-sqlite-to-mysql into lp:~charmers/charms/precise/keystone/trunk

Proposed by Nick Moffitt
Status: Superseded
Proposed branch: lp:~nick-moffitt/charms/precise/keystone/migrate-sqlite-to-mysql
Merge into: lp:~charmers/charms/precise/keystone/trunk
Diff against target: 141 lines (+81/-14)
1 file modified
hooks/keystone-hooks (+81/-14)
To merge this branch: bzr merge lp:~nick-moffitt/charms/precise/keystone/migrate-sqlite-to-mysql
Reviewer Review Type Date Requested Status
Adam Gandelman (community) Needs Fixing
charmers Pending
Review via email: mp+149334@code.launchpad.net

This proposal has been superseded by a proposal from 2014-02-19.

Description of the change

This change uses sqlfairy (http://packages.ubuntu.com/precise/libsql-translator-perl) to migrate the schema and then all rows from the default sqlite database over to the new database when the mysql relation fires for the first time. The rationale is that users may not realize that they're using sqlite until it's too late, and adding the relation caused a loss of all authentication data.

The SQL generated from sqlfairy needed some manipulation in order to work.
In particular:

    * "UNIQUE `` (`name`)" commands needed a UID of some sort between the ``
      for mysql to accept them
    * As we're running these mysql 'LOAD DATA' commands from a remote
      client, 'LOAD DATA INFILE' needed to become 'LOAD DATA LOCAL INFILE',
      and the mysql client needed to be explicitly told to allow this
      syntax.

The install hook edits configuration entries, risking loss of idempotence. Instead of just re-running it in the upgrade hook, I've replicated the apt-get commands.

To post a comment you must log in.
Revision history for this message
Adam Gandelman (gandelman-a) wrote :

Hey Nick-

Thanks for submitting. This is quite useful. Tested using a sqlite database /w a few thousand users and the migration worked great. I'm just a bit confused about the last bit WRT upgrade. The added 'apt-get' commands you added to the install hook, which currently isn't called on charm upgrade. Did you mean to add a new upgrade hook that re-runs apt-get update only?

review: Needs Information
Revision history for this message
Nick Moffitt (nick-moffitt) wrote :

Adam, thanks for helping to test this!

In my initial use case, we had a system that we realized with some shock had been running with sqlite, and we were about to see an increase in the volume of the database. Since this charm was already running on a production system, I needed to make sure that the new package dependencies (sqlfairy itself, for instance) were installed before the relation hook fired.

In my understanding, no configuration steps should be taken in install hooks, since a fresh install will get a config-changed after the install hook fires anyway. Unfortunately this charm seemed to have done some config steps in the install hook, so I was nervous about trusting its idempotence and calling it from my upgrade hook.

Since this was meant to roll out a change to our production service, I took the simplest step forward that would obviously work correctly. So that's why there's duplication of the apt-get steps in the upgrade hook function.

I would love to see that bit detangled somewhat, but it seemed out of scope for this merge.

Revision history for this message
Nick Moffitt (nick-moffitt) wrote :

Oh, I see now that I may have mispasted during the merge! I'll tidy that up.

Revision history for this message
Adam Gandelman (gandelman-a) wrote :

Nick-

Yes, you're right about the config options being set in the install hook being a bug. They were originally there to work on old deficiencies in packaging that are fixed now, and could probably be dropped from there / moved to config_changed.

That said, I'm not sure about duplicating the apt-get commands wholesale in upgrade-charm. As its proposed here, if there has been a newer package published to the archive for Keystone (or any other package that may have charm modified configs) since the last charm upgrade, apt-get will block waiting for user guidance on how to handle the config changes (merge, overwrite, shell, etc) and the upgrade hook will never complete. I see a couple of options:

- Call apt-get /w "--option Dpkg::Options::=--force-confnew -n" to ensure we keep our juju modified configs in place. This could be problematic, though, if the updated packages contain critical config file changes, too.

- Avoid calling apt-get in the upgrade hook and instead late install the packages required for migration as needed in migrate_db(), similar to how subprocess is being imported late there.

The latter seems safest to me, since we're only acting on the migration related packages, and ideally only gets run once upon relating to a new mysql server. What do you think?

review: Needs Fixing
Revision history for this message
Nick Moffitt (nick-moffitt) wrote :

So it sounds like even the apt commands in the install hook are not idempotent! If that's the case, how do we solve this more generally?

Revision history for this message
Nick Moffitt (nick-moffitt) wrote :

And I think you're right about the apt commands being best suited to the particular hook. I'll have a go at that next.

Revision history for this message
Adam Gandelman (gandelman-a) wrote :

Marking 'Rejected' to get it out of the charm review queue, feel free to mark as 'Needs Review' when ready.

Revision history for this message
Nick Moffitt (nick-moffitt) wrote :

I've updated my branch, but this MP seems to still have the old diff. I've marked it back to "needs review" in the hopes that it will catch up as a result.

Revision history for this message
Juan L. Negron (negronjl) wrote :

I'm deferring this to Adam.

-Juan

Revision history for this message
Adam Gandelman (gandelman-a) wrote :

Hey Nick-- Sorry for the delay on this. We're in the process of testing and merging back 6 mo. worth of charm work to the main cs:precise/keystone branch. I'm hesitant to merge this until thats done, but the changes you've committed in response to my comments look good. We will hopefully have the main branch updated before next week, and I'll merge this in as soon as we do.

Revision history for this message
Adam Gandelman (gandelman-a) wrote :

Hey Nick-

We've landed all of the pending keystone stuff. Any chance you can rebase this against current state of lp:charms/keystone? We'll be busy for a while migrating existing shell-based openstack stuff to python, so the keystone charm should be relatively quite in the meantime. Thanks

Unmerged revisions

42. By Nick Moffitt

Moved sqlfairy installation insurance down to the db_changed hook, so that it happens only when it's needed.
This is in response to https://code.launchpad.net/~nick-moffitt/charms/precise/keystone/migrate-sqlite-to-mysql/+merge/149334/comments/327313

41. By Nick Moffitt

Fix the half-finished upgrade-charm work.

40. By Nick Moffitt

Automatically migrate the database schema and contents when moving from sqlite to mysql.

This change uses sqlfairy (http://packages.ubuntu.com/precise/libsql-translator-perl) to migrate the schema and then all rows from the default sqlite database over to the new database when the mysql relation fires for the first time. The rationale is that users may not realize that they're using sqlite until it's too late, and adding the relation caused a loss of all authentication data.

The SQL generated from sqlfairy needed some manipulation in order to work.
In particular:

    * "UNIQUE `` (`name`)" commands needed a UID of some sort between the ``
      for mysql to accept them
    * As we're running these mysql 'LOAD DATA' commands from a remote
      client, 'LOAD DATA INFILE' needed to become 'LOAD DATA LOCAL INFILE',
      and the mysql client needed to be explicitly told to allow this
      syntax.

The install hook edits configuration entries, risking loss of idempotence. Instead of just re-running it in the upgrade hook, I've replicated the apt-get commands.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'hooks/keystone-hooks'
--- hooks/keystone-hooks 2012-12-12 03:52:01 +0000
+++ hooks/keystone-hooks 2013-02-26 18:13:22 +0000
@@ -8,7 +8,7 @@
88
9config = config_get()9config = config_get()
1010
11packages = "keystone python-mysqldb pwgen"11packages = "keystone python-mysqldb pwgen sqlfairy libdbd-sqlite3-perl mysql-client-core-5.5"
12service = "keystone"12service = "keystone"
1313
14# used to verify joined services are valid openstack components.14# used to verify joined services are valid openstack components.
@@ -75,6 +75,12 @@
75 time.sleep(5)75 time.sleep(5)
76 ensure_initial_admin(config)76 ensure_initial_admin(config)
7777
78def upgrade_charm():
79 # ensure we have all necessary packages for any relations or config
80 # changes.
81 execute("apt-get update", die=True)
82 execute("apt-get -y install %s" % packages, die=True, echo=True)
83
78def db_joined():84def db_joined():
79 relation_data = { "database": config["database"],85 relation_data = { "database": config["database"],
80 "username": config["database-user"],86 "username": config["database-user"],
@@ -82,31 +88,91 @@
82 relation_set(relation_data)88 relation_set(relation_data)
8389
84def db_changed():90def db_changed():
91 def migrate_db(db_uri, relation_data):
92 import subprocess
93 schemacmd = ['sqlt',
94 '-f', 'DBI', '--dsn', 'dbi:SQLite:{}'.format(db_uri.path),
95 '-t', 'MySQL',
96 '--add-drop-table']
97 p = subprocess.Popen(schemacmd, stdout=subprocess.PIPE)
98 idx = 0
99 schema = ''
100 for line in p.stdout:
101 if 'UNIQUE ``' in line:
102 # Anonymous indexes collide, so we give a random name
103 line = line.replace('``', '`idx{}`'.format(idx))
104 idx += 1
105 schema += line
106 mysqlcmd = ['mysql', '--local-infile',
107 '--host={private-address}'.format(**relation_data),
108 '--user={database-user}'.format(**config),
109 '--password={password}'.format(**relation_data),
110 '--database={database}'.format(**config)]
111 p = subprocess.Popen(mysqlcmd, stdout=subprocess.PIPE,
112 stdin=subprocess.PIPE)
113 print schema
114 p.communicate(schema)
115 if p.returncode:
116 juju_log("Schema failed to update.")
117 raise subprocess.CalledProcessError(p.returncode, mysqlcmd)
118 # This next step can be confusing: sqlt generates a perl script we
119 # then run to do the actual data migration.
120 make_dumpercmd = ['sqlt',
121 '-f', 'DBI', '--dsn', 'dbi:SQLite:{}'.format(db_uri.path),
122 '-t', 'Dumper',
123 '--use-same-auth']
124 p = subprocess.check_call(make_dumpercmd,
125 stdout=open('db_migrate.pl', 'w'))
126 run_dumpercmd = ['perl', 'db_migrate.pl', '--mysql-loadfile']
127 out = subprocess.check_output(run_dumpercmd)
128 # We need to load data from this system, not the one running mysql
129 loadfiledata = out.replace('LOAD DATA INFILE',
130 'LOAD DATA LOCAL INFILE')
131 p = subprocess.Popen(mysqlcmd, stdout=subprocess.PIPE,
132 stdin=subprocess.PIPE)
133 p.communicate(loadfiledata)
134 if p.returncode:
135 juju_log("Data failed to update.")
136 raise subprocess.CalledProcessError(p.returncode, mysqlcmd)
137
138 from urlparse import urlparse
139 from ConfigParser import ConfigParser
140
85 relation_data = relation_get_dict()141 relation_data = relation_get_dict()
86 if ('password' not in relation_data or142 if ('password' not in relation_data or
87 'private-address' not in relation_data):143 'private-address' not in relation_data):
88 juju_log("private-address or password not set. Peer not ready, exit 0")144 juju_log("private-address or password not set. Peer not ready, exit 0")
89 exit(0)145 exit(0)
146
147 keyconf = ConfigParser()
148 keyconf.read(keystone_conf)
149 db_uri = urlparse(keyconf.get('sql','connection'))
150
90 update_config_block('sql', connection="mysql://%s:%s@%s/%s" %151 update_config_block('sql', connection="mysql://%s:%s@%s/%s" %
91 (config["database-user"],152 (config["database-user"],
92 relation_data["password"],153 relation_data["password"],
93 relation_data["private-address"],154 relation_data["private-address"],
94 config["database"]))155 config["database"]))
95 execute("service keystone stop", echo=True)156 execute("service keystone stop", echo=True)
96 execute("keystone-manage db_sync", echo=True)
97 execute("service keystone start")
98 time.sleep(5)
99 ensure_initial_admin(config)
100157
101 # If the backend database has been switched to something new and there158 if db_uri.scheme == 'sqlite':
102 # are existing identity-service relations,, service entries need to be159 juju_log("Migrating schema and data from sqlite to mysql")
103 # recreated in the new database. Re-executing identity-service-changed160 migrate_db(db_uri, relation_data)
104 # will do this.161 execute("service keystone start")
105 for id in relation_ids(relation_name='identity-service'):162 else:
106 for unit in relation_list(relation_id=id):163 execute("keystone-manage db_sync", echo=True)
107 juju_log("Re-exec'ing identity-service-changed for: %s - %s" %164 execute("service keystone start")
108 (id, unit))165 time.sleep(5)
109 identity_changed(relation_id=id, remote_unit=unit)166 ensure_initial_admin(config)
167 # If the backend database has been switched to something new and
168 # there are existing identity-service relations,, service entries
169 # need to be recreated in the new database. Re-executing
170 # identity-service-changed will do this.
171 for id in relation_ids(relation_name='identity-service'):
172 for unit in relation_list(relation_id=id):
173 juju_log("Re-exec'ing identity-service-changed for: %s - %s" %
174 (id, unit))
175 identity_changed(relation_id=id, remote_unit=unit)
110176
111def identity_joined():177def identity_joined():
112 """ Do nothing until we get information about requested service """178 """ Do nothing until we get information about requested service """
@@ -254,6 +320,7 @@
254320
255hooks = {321hooks = {
256 "install": install_hook,322 "install": install_hook,
323 "upgrade-charm": upgrade_charm,
257 "shared-db-relation-joined": db_joined,324 "shared-db-relation-joined": db_joined,
258 "shared-db-relation-changed": db_changed,325 "shared-db-relation-changed": db_changed,
259 "identity-service-relation-joined": identity_joined,326 "identity-service-relation-joined": identity_joined,
260327
=== added symlink 'hooks/upgrade-charm'
=== target is u'keystone-hooks'

Subscribers

People subscribed via source and target branches

to all changes: