]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Implements bp migrate-nova-volumes-to-cinder
authorJohn Griffith <john.griffith@solidfire.com>
Wed, 8 Aug 2012 19:33:08 +0000 (13:33 -0600)
committerJohn Griffith <john.griffith@solidfire.com>
Tue, 14 Aug 2012 22:33:00 +0000 (16:33 -0600)
  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

bin/cinder-manage
cinder/exception.py

index e6b9b8945253a3d7075beb03181fb9080919d0c5..566c2b63761a53702ad18dd59638cd9ce92f5c79 100755 (executable)
   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),
 ]
 
 
index 298edc62cf17ad0d1823f3bd55a510d8aae994f5..2ceeeec7c9243a0a37107894453a7863ac007e5a 100644 (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.")