#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (c) Greenplum Inc 2010. All Rights Reserved. # # System imports import os import sys import signal from optparse import OptionGroup # import GPDB modules try: from gppylib.db import dbconn from gppylib.gpparseopts import OptParser, OptChecker from gppylib.gparray import * from gppylib.gplog import * from gppylib.commands import unix, gp, base from gppylib.db import dbconn from gppylib import gparray, pgconf from gppylib.operations.deletesystem import validate_pgport from gppylib.userinput import * except ImportError, e: sys.exit('ERROR: Cannot import modules. Please check that you ' 'have sourced greenplum_path.sh. Detail: ' + str(e)) EXECNAME = os.path.split(__file__)[-1] DEFAULT_BATCH_SIZE = 32 MAX_BATCH_SIZE = 128 _description = """ """ _usage = """ """ # Generic exception for all things activatestandby class GpDeleteSystemException(Exception): pass # ------------------------------------------------------------------------- # parseargs() - parses and validates command line args # ------------------------------------------------------------------------- def parseargs(): parser = OptParser(option_class=OptChecker, description=' '.join(_description.split()), version='%prog version $Revision$') parser.setHelp([]) parser.remove_option('-h') parser.remove_option('--version') # General options section optgrp = OptionGroup(parser, 'General options') optgrp.add_option('-?', '--help', dest='help', action='store_true', help='Display this help message and exit') optgrp.add_option('-v', '--version', dest='version', action='store_true', help='Display version information and exit.') parser.add_option_group(optgrp) # Logging options section optgrp = OptionGroup(parser, 'Logging options') optgrp.add_option('-l', '--logfile', type='string', default=None, help='Alternative log file directory') optgrp.add_option('-D', '--verbose', help='Enable debug logging.', dest='verbose', default=False, action='store_true') parser.add_option_group(optgrp) # Standby activation options section optgrp = OptionGroup(parser, 'Delete system options') optgrp.add_option('-d', '--master-data-directory', dest='master_data_dir', type='string', help='Master data directory.') optgrp.add_option('-f', '--force', action='store_true', help='Force deletion. Ignore any database backup files.') optgrp.add_option('-B', '--batch-size', type='int', dest='batch_size', default=DEFAULT_BATCH_SIZE, help='Number of batches to run in parallel. (Default %s)' % DEFAULT_BATCH_SIZE) parser.add_option_group(optgrp) # Parse the command line arguments (options, args) = parser.parse_args() if options.help: parser.print_help() parser.exit(0, None) if options.version: parser.print_version() parser.exit(0, None) # check we got the -d option if not options.master_data_dir: logger.info('Option -d or --master-data-directory not set. Checking environment variable MASTER_DATA_DIRECTORY') env_master_data_dir = os.getenv('MASTER_DATA_DIRECTORY', None) # check for environment variable MASTER_DATA_DIRECTORY if not env_master_data_dir: logger.fatal('Both -d parameter and MASTER_DATA_DIRECTORY environment variable are not set.') logger.fatal('Required option master data directory is missing.') parser.exit(2, None) options.master_data_dir = env_master_data_dir # We have to normalize this path for a later comparison options.master_data_dir = os.path.normpath(options.master_data_dir) # Check that master data directory exists if not os.path.exists(options.master_data_dir) or not os.path.isdir(options.master_data_dir): logger.fatal('Master data directory supplied %s does not exist' % options.master_data_dir) parser.exit(2, None) if options.logfile and not os.path.exists(options.logfile): logger.fatal('Log directory %s does not exist.' % options.logfile) parser.exit(2, None) options.pgport = validate_pgport(options.master_data_dir) # Set log level if options.verbose: enable_verbose_logging() # verify batch size if options.batch_size < 1 or options.batch_size > MAX_BATCH_SIZE: logger.fatal('--batch-size value must be from 1 to %s' % MAX_BATCH_SIZE) parser.exit(2, None) # There shouldn't be any args if len(args) > 0: logger.error('Unknown arguments:') for arg in args: logger.error(' %s' % args) parser.exit(2, None) return options, args # ------------------------------------------------------------------------- # display_params() - displays delete system parameters. # ------------------------------------------------------------------------- def display_params(options, dburl, standby, segments, dumpDirsExist): global g_warnings_generated logger.info('Greenplum Instance Deletion Parameters') logger.info('--------------------------------------') logger.info('Greenplum Master hostname = %s' % dburl.pghost) logger.info('Greenplum Master data directory = %s' % options.master_data_dir) logger.info('Greenplum Master port = %s' % dburl.pgport) if standby: logger.info('Greenplum Master standby host = %s' % standby.getSegmentHostName()) logger.info('Greenplum Master standby data directory = %s' % standby.getSegmentDataDirectory()) logger.info('Greenplum Master standby port = %s' % standby.getSegmentPort()) if options.force: logger.info('Greenplum Force delete of dump files = ON') else: logger.info('Greenplum Force delete of dump files = OFF') logger.info('Batch size = %s' % options.batch_size) logger.info('--------------------------------------') logger.info(' Segment Instance List ') logger.info('--------------------------------------') logger.info('Host:Datadir:Port') for segdb in segments: host = segdb.getSegmentHostName() datadir = segdb.getSegmentDataDirectory() port = segdb.getSegmentPort() logger.info('%s:%s:%s' % (host, datadir, port)) yn = ask_yesno('', 'Continue with Greenplum instance deletion?', 'N') if yn: logger.info('FINAL WARNING, you are about to delete the Greenplum instance') logger.info('on master host %s.' % dburl.pghost) if dumpDirsExist and options.force: logger.warn('There are database dump files, these will be DELETED if you continue!') g_warnings_generated = True yn = ask_yesno('', 'Continue with Greenplum instance deletion?', 'N') if not yn: raise GpDeleteSystemException('User canceled') else: raise GpDeleteSystemException('User canceled') def getTablespaceDirs(): ''' Create list of user-created tablespace locations for each host ''' tblspclocs = {} gettblspcoids_sql = "select oid from pg_tablespace where spcname not in ('pg_default', 'pg_global')" gettblspclocs_sql = "select c.hostname, t.tblspc_loc from gp_tablespace_location(%s) t, gp_segment_configuration c where t.gp_segment_id = c.content group by c.hostname, t.tblspc_loc" # Get the tablespace oids with dbconn.connect(dbconn.DbURL()) as conn: tblspcoids = dbconn.execSQL(conn, gettblspcoids_sql).fetchall() # Use the tablespace oids to get the tablespace locations for row in tblspcoids: tblspcoid = row[0] with dbconn.connect(dbconn.DbURL()) as conn: query_results = dbconn.execSQL(conn, gettblspclocs_sql % tblspcoid).fetchall() for query_result in query_results: if query_result[0] in tblspclocs: tblspclocs[query_result[0]].append(query_result[1]) else: tblspclocs[query_result[0]] = [query_result[1]] return tblspclocs # ------------------------------------------------------------------------- # delete_system() - deletes a GPDB system # ------------------------------------------------------------------------- def delete_cluster(options): global g_warnings_generated # get gparray object logger.info('Getting segment information...') dburl = dbconn.DbURL(port=options.pgport) try: array = gparray.GpArray.initFromCatalog(dburl, True) tablespaceDirs = getTablespaceDirs() except Exception, ex: raise GpDeleteSystemException('Failed to get database configuration: %s' % ex.__str__()) # get all segdbs segments = array.getDbList() standby = array.standbyMaster # Display the options display_params(options, dburl, standby, segments, dump_files_exist) # stop database logger.info('Stopping database...') try: cmd = gp.GpStop('stop database', fast=True, datadir=options.master_data_dir) cmd.run(validateAfter=True) except: results = cmd.get_results() if results.rc > 1: raise GpDeleteSystemException('Failed to stop database.') logger.warn('Warnings were generated while stopping the database.') g_warnings_generated = True try: # From this point on we don't want ctrl-c to happen signal.signal(signal.SIGINT, signal.SIG_IGN) # create pool pool = base.WorkerPool(numWorkers=options.batch_size) try: logger.info('Deleting tablespace directories...') for segmentHost in tablespaceDirs: for tablespaceDir in tablespaceDirs[segmentHost]: logger.debug('Queueing up command to remove %s:%s' % (segmentHost, tablespaceDir)) cmd = unix.RemoveDirectory('remove tablespace dir', tablespaceDir, ctxt=base.REMOTE, remoteHost=segmentHost) pool.addCommand(cmd) logger.info('Waiting for worker threads to delete tablespace dirs...') finally: pool.join() pool.haltWork() pool.joinWorkers() # create pool pool = base.WorkerPool(numWorkers=options.batch_size) try: logger.info('Deleting segments and removing data directories...') for segdb in segments: segmentDataDirectory = segdb.getSegmentDataDirectory() logger.debug('Queueing up command to remove %s:%s' % (segdb.getSegmentHostName(), segmentDataDirectory)) cmd = unix.RemoveDirectory('remove data dir', segmentDataDirectory, ctxt=base.REMOTE, remoteHost=segdb.getSegmentHostName()) pool.addCommand(cmd) logger.info('Waiting for worker threads to complete...') finally: pool.join() pool.haltWork() pool.joinWorkers() finally: # Re-enable ctrl-c signal.signal(signal.SIGINT, signal.default_int_handler) ############# if __name__ == '__main__': # ------------------------------------------------------------------------- # main # ------------------------------------------------------------------------- g_warnings_generated = False g_errors_generated = False # setup logging logger = get_default_logger() setup_tool_logging(EXECNAME, unix.getLocalHostname(), unix.getUserName()) # parse args and options (options, args) = parseargs() # if we got a new log dir, we can now set it up. if options.logfile: setup_tool_logging(EXECNAME, unix.getLocalHostname(), unix.getUserName(), logdir=options.logfile) try: # save off cwd cwd = os.getcwd() # chdir to home to prevent from trying to delete a dir while we are in it home = os.getenv('USERPROFILE') or os.getenv('HOME') os.chdir(home) # do the delete delete_cluster(options) # now try to go back into the original cwd try: os.chdir(cwd) except: # we can ignore this as cwd must no longer exist pass except GpDeleteSystemException, ex: g_errors_generated = True if ex.__str__() == 'User canceled': logger.info(ex.__str__()) else: logger.fatal(ex.__str__()) if options.verbose: logger.exception(ex) except Exception, ex: g_errors_generated = True logger.fatal('Error deleting system: %s' % ex.__str__()) if options.verbose: logger.exception(ex) finally: if g_errors_generated: logger.error('Delete system failed') sys.exit(2) elif g_warnings_generated: logger.warn('Delete system completed but warnings were generated.') sys.exit(1) else: logger.info('Delete system successful.') sys.exit(0)