From 11545df5cb4fde935adec8160154e591cde8d1b9 Mon Sep 17 00:00:00 2001 From: John Griffith Date: Wed, 8 Aug 2012 13:33:08 -0600 Subject: [PATCH] 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 --- bin/cinder-manage | 134 +++++++++++++++++++++++++++++++++++++++++--- cinder/exception.py | 4 ++ 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/bin/cinder-manage b/bin/cinder-manage index e6b9b8945..566c2b637 100755 --- a/bin/cinder-manage +++ b/bin/cinder-manage @@ -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='', + 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='', + 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='', + help='[login@src_host:]/opt/stack/nova/volumes/') + @args('--dest', dest='dest_tgts', metavar='', + 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), ] diff --git a/cinder/exception.py b/cinder/exception.py index 298edc62c..2ceeeec7c 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -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.") -- 2.45.2