Implements bp migrate-nova-volumes-to-cinder

Helper cmds to transition from nova to cinder

  Implements an import section in cinder-manage to transfer
  applicable tables from local or remote Nova database into a
  fresh Cinder database.

  Also implements optional method to copy persistent target files.

Change-Id: I2e655e26c55f1986f3b1554726cead9e73ee9bd6
This commit is contained in:
John Griffith 2012-08-08 13:33:08 -06:00
parent 5f31f6e697
commit 11545df5cb
2 changed files with 129 additions and 9 deletions

View File

@ -54,17 +54,15 @@
CLI interface for cinder management.
"""
import ast
import errno
import gettext
import json
import math
import netaddr
import optparse
import os
import StringIO
import sys
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# If ../cinder/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
@ -83,12 +81,9 @@ from cinder import exception
from cinder import flags
from cinder.openstack.common import log as logging
from cinder.openstack.common import cfg
from cinder.openstack.common import importutils
from cinder.openstack.common import rpc
from cinder import quota
from cinder import utils
from cinder import version
from cinder.volume import volume_types
FLAGS = flags.FLAGS
@ -236,6 +231,126 @@ class VersionCommands(object):
self.list()
class ImportCommands(object):
"""Methods for importing Nova volumes to Cinder.
EXPECTATIONS:
These methods will do two things:
1. Import relevant Nova DB info in to Cinder
2. Import persistent tgt files from Nova to Cinder (see copy_tgt_files)
If you're using VG's (local storage) for your backend YOU MUST install
Cinder on the same node that you're migrating from.
"""
def __init__(self):
pass
def _map_table(self, table):
class Mapper(declarative_base()):
__table__ = table
return Mapper
def _open_session(self, con_info):
engine = create_engine(con_info,
convert_unicode=True,
echo=True)
session = sessionmaker(bind=engine)
return (session(), engine)
def _backup_cinder_db(self):
#First, dump the dest_db as a backup incase this goes wrong
cinder_dump = utils.execute('mysqldump', 'cinder')
if 'Dump completed on' in cinder_dump[0]:
with open('./cinder_db_bkup.sql', 'w+') as fo:
for line in cinder_dump:
fo.write(line)
else:
raise exception.InvalidResults()
def _import_db(self, src_db, dest_db, backup_db):
# Remember order matters due to FK's
table_list = ['sm_flavors',
'sm_backend_config',
'snapshots',
'volume_types',
'volumes',
'iscsi_targets',
'sm_volume',
'volume_metadata',
'volume_type_extra_specs']
if backup_db > 0:
if 'mysql:' not in dest_db:
print (_('Sorry, only mysql backups are supported!'))
raise exception.InvalidRequest()
else:
self._backup_cinder_db()
(src, src_engine) = self._open_session(src_db)
src_meta = MetaData(bind=src_engine)
(dest, dest_engine) = self._open_session(dest_db)
for table_name in table_list:
print (_('Importing table %s...' % table_name))
table = Table(table_name, src_meta, autoload=True)
new_row = self._map_table(table)
columns = table.columns.keys()
for row in src.query(table).all():
data = dict([(str(column), getattr(row, column))
for column in columns])
dest.add(new_row(**data))
dest.commit()
@args('--src', dest='src_db', metavar='<Nova DB>',
help='db-engine://db_user[:passwd]@db_host[:port]\t\t'
'example: mysql://root:secrete@192.168.137.1')
@args('--dest', dest='dest_db', metavar='<Cinder DB>',
help='db-engine://db_user[:passwd]@db_host[:port]\t\t'
'example: mysql://root:secrete@192.168.137.1')
@args('--backup', dest='backup_db', metavar='<0|1>',
help='Perform mysqldump of cinder db before writing to it')
def import_db(self, src_db, dest_db, backup_db=1):
"""Import relevant volume DB entries from Nova into Cinder.
NOTE:
Your Cinder DB should be clean WRT volume entries.
NOTE:
We take an sqldump of the cinder DB before mods
If you're not using mysql, set backup_db=0
and create your own backup.
"""
src_db = '%s/nova' % src_db
dest_db = '%s/cinder' % dest_db
self._import_db(src_db, dest_db, backup_db)
@args('--src', dest='src_tgts', metavar='<src tgts>',
help='[login@src_host:]/opt/stack/nova/volumes/')
@args('--dest', dest='dest_tgts', metavar='<dest tgts>',
help='[login@src_host:/opt/stack/cinder/volumes/]')
def copy_ptgt_files(self, src_tgts, dest_tgts=None):
"""Copy persistent scsi tgt files from nova to cinder.
Default destination is FLAGS.volume_dir or state_path/volumes/
PREREQUISITES:
Persistent tgts were introduced in Folsom. If you're running
Essex or other release, this script is unnecessary.
NOTE:
If you're using local VG's and LVM for your nova volume backend
there's no point in copying these files over. Leave them on
your Nova system as they won't do any good here.
"""
if dest_tgts is None:
try:
dest_tgts = FLAGS.volumes_dir
except:
dest_tgts = '%s/volumes' % FLAGS.state_path
utils.execute('rsync', '-avz', src_tgts, dest_tgts)
class VolumeCommands(object):
"""Methods for dealing with a cloud in an odd state"""
@ -492,6 +607,7 @@ CATEGORIES = [
('sm', StorageManagerCommands),
('version', VersionCommands),
('volume', VolumeCommands),
('migrate', ImportCommands),
]

View File

@ -239,6 +239,10 @@ class InvalidRequest(Invalid):
message = _("The request is invalid.")
class InvalidResults(Invalid):
message = _("The results are invalid.")
class InvalidSignature(Invalid):
message = _("Invalid signature %(signature)s for user %(user)s.")