def __init__(self, options, args): self.master_datadir = options.masterDataDirectory # TODO: AK: Program logic should not be dictating master, standby, and segment information # In other words, the fundamental Operations should have APIs that preclude the need for this. self.master_host = None self.standby_host = None self.segment_host_list = None self.query = options.query self.build = options.build self.install = options.install self.remove = options.remove self.update = options.update self.clean = options.clean self.migrate = options.migrate self.interactive = options.interactive self.filename = options.filename # only one of the following may be provided: --install, --remove, --update, --query, --build, --clean, --migrate count = sum([ 1 for opt in [ 'install', 'remove', 'update', 'query', 'build', 'clean', 'migrate' ] if getattr(self, opt) ]) if count != 1: raise ExceptionNoStackTraceNeeded( 'Exactly one of the following must be provided: --install, --remove, -update, --query, --clean, --migrate' ) if self.query: # gppkg -q can be supplemented with --info, --list, --all count = sum([ 1 for opt in ['info', 'list', 'all'] if options.__dict__[opt] ]) if count > 1: raise ExceptionNoStackTraceNeeded( 'For --query, at most one of the following can be provided: --info, --list, --all' ) # for all query options other than --all, a package path must be provided if not options.all and len(args) != 1: raise ExceptionNoStackTraceNeeded( 'A package must be specified for -q, -q --info, and -q --list.' ) if options.info: self.query = (QueryPackage.INFO, args[0]) elif options.list: self.query = (QueryPackage.LIST, args[0]) elif options.all: self.query = (QueryPackage.ALL, None) else: self.query = (None, args[0]) elif self.migrate: if len(args) != 2: raise ExceptionNoStackTraceNeeded( 'Invalid syntax, expecting "gppkg --migrate <from_gphome> <to_gphome>".' ) self.migrate = (args[0], args[1])
def run(self): if self.build: if self.filename: BuildGppkg(self.build, self.filename).run() else: BuildGppkg(self.build, None).run() return #Check for RPM and Solaris OS if curr_platform == SUNOS: raise ExceptionNoStackTraceNeeded( 'gppkg is not supported on Solaris') try: if platform.linux_distribution()[0] == 'Ubuntu': cmd = Command(name='Check for dpkg', cmdStr='dpkg --version') cmd.run(validateAfter=True) else: cmd = Command(name='Check for rpm', cmdStr='rpm --version') cmd.run(validateAfter=True) results = cmd.get_results().stdout.strip() rpm_version_string = results.split(' ')[-1] if not rpm_version_string.startswith('4.'): raise ExceptionNoStackTraceNeeded( 'gppkg requires rpm version 4.x') except ExecutionError, ex: results = ex.cmd.get_results().stderr.strip() if len(results) != 0 and 'not found' in results: raise ExceptionNoStackTraceNeeded( 'gppkg requires RPM to be available in PATH')
def _getParsedRow(self, filename, lineno, line): groups = line.split() # NOT line.split(' ') due to MPP-15675 if len(groups) not in [1, 2]: msg = "line %d of file %s: expected 1 or 2 groups but found %d" % (lineno, filename, len(groups)) raise ExceptionNoStackTraceNeeded(msg) parts = groups[0].split('|') if len(parts) != 3: msg = "line %d of file %s: expected 3 parts on failed segment group, obtained %d" % ( lineno, filename, len(parts)) raise ExceptionNoStackTraceNeeded(msg) address, port, datadir = parts check_values(lineno, address=address, port=port, datadir=datadir) row = { 'failedAddress': address, 'failedPort': port, 'failedDataDirectory': datadir, 'lineno': lineno } if len(groups) == 2: parts2 = groups[1].split('|') if len(parts2) != 3: msg = "line %d of file %s: expected 3 parts on new segment group, obtained %d" % ( lineno, filename, len(parts2)) raise ExceptionNoStackTraceNeeded(msg) address2, port2, datadir2 = parts2 check_values(lineno, address=address2, port=port2, datadir=datadir2) row.update({ 'newAddress': address2, 'newPort': port2, 'newDataDirectory': datadir2 }) return row
def execute(self): """ TODO: Improve with grouping by host and ParallelOperation dispatch. """ gparray = GpArray.initFromCatalog(dbconn.DbURL(port=self.master_port), utility=True) primaries = [ seg for seg in gparray.getDbList() if seg.isSegmentPrimary(current_role=True) ] dump_count = 0 for seg in primaries: if seg.isSegmentDown(): """ Why must every Segment function have the word Segment in it ?! """ raise ExceptionNoStackTraceNeeded( "Host %s dir %s dbid %d marked as invalid" % (seg.getSegmentHostName(), seg.getSegmentDataDirectory(), seg.getSegmentDbId())) path = os.path.join(seg.getSegmentDataDirectory(), DUMP_DIR, self.restore_timestamp[0:8]) host = seg.getSegmentHostName() path = os.path.join( path, "%s0_%d_%s" % (DBDUMP_PREFIX, seg.getSegmentDbId(), self.restore_timestamp)) if self.compress: path += ".gz" exists = CheckRemoteFile(path, host).run() if not exists: raise ExceptionNoStackTraceNeeded( "No dump file on %s at %s" % (seg.getSegmentHostName(), path))
def parse_gpmovemirrors_line(filename, lineno, line): """ Parse a line in the gpmovemirrors configuration file other than the first. >>> line = "[::1]:40001:/Users/ctaylor/data/m2/gpseg1 [::2]:40101:/Users/ctaylor/data/m2/gpseg1" >>> rows = parse_gpmovemirrors_line('file', 1, line) >>> rows["oldAddress"], rows["newAddress"] ('::1', '::2') """ groups = len(line.split()) if groups != 2: msg = "need two groups of fields delimited by a space for old and new mirror, not %d" % groups raise ExceptionNoStackTraceNeeded("%s:%s:%s LINE >>%s\n%s" % (filename, lineno, caller(), line, msg)) rows = {} p = LineParser(caller(), filename, lineno, line) p.handle_field('[oldAddress]', rows) # [oldAddress] indicates possible IPv6 address p.handle_field('oldPort', rows) p.handle_field('oldDataDirectory', rows, delimiter=' ', stripchars=' \t') # MPP-15675 note stripchars here and next line p.handle_field('[newAddress]', rows, stripchars=' \t') # [newAddress] indicates possible IPv6 address p.handle_field('newPort', rows) p.handle_field('newDataDirectory', rows) if p.rest is not None: msg = "unexpected characters after mirror fields >>%s" % p.rest raise ExceptionNoStackTraceNeeded("%s:%s:%s LINE >>%s\n%s" % (filename, lineno, caller(), line, msg)) return rows
def _parseConfigFile(config_file): """ Parse the config file :param config_file: :return: List of dictionaries with each dictionary containing the failed and failover information?? """ rows = [] with open(config_file) as f: for lineno, line in line_reader(f): groups = line.split() # NOT line.split(' ') due to MPP-15675 if len(groups) not in [1, 2]: msg = "line %d of file %s: expected 1 or 2 groups but found %d" % ( lineno, config_file, len(groups)) raise ExceptionNoStackTraceNeeded(msg) parts = groups[0].split('|') if len(parts) != 3: msg = "line %d of file %s: expected 3 parts on failed segment group, obtained %d" % ( lineno, config_file, len(parts)) raise ExceptionNoStackTraceNeeded(msg) address, port, datadir = parts check_values(lineno, address=address, port=port, datadir=datadir) datadir = normalizeAndValidateInputPath( datadir, f.name, lineno) row = { 'failedAddress': address, 'failedPort': port, 'failedDataDirectory': datadir, 'lineno': lineno } if len(groups) == 2: parts2 = groups[1].split('|') if len(parts2) != 3: msg = "line %d of file %s: expected 3 parts on new segment group, obtained %d" % ( lineno, config_file, len(parts2)) raise ExceptionNoStackTraceNeeded(msg) address2, port2, datadir2 = parts2 check_values(lineno, address=address2, port=port2, datadir=datadir2) datadir2 = normalizeAndValidateInputPath( datadir2, f.name, lineno) row.update({ 'newAddress': address2, 'newPort': port2, 'newDataDirectory': datadir2 }) rows.append(row) RecoveryTripletsUserConfigFile._validate(rows) return rows
def __init__(self, options, args): self.coordinator_datadir = options.coordinatorDataDirectory # TODO: AK: Program logic should not be dictating coordinator, standby, and segment information # In other words, the fundamental Operations should have APIs that preclude the need for this. self.coordinator_host = None self.standby_host = None self.segment_host_list = None self.query = options.query self.build = options.build self.install = options.install self.remove = options.remove self.update = options.update self.clean = options.clean self.migrate = options.migrate self.interactive = options.interactive self.filename = options.filename # only one of the following may be provided: --install, --remove, --update, --query, --build, --clean, --migrate count = sum([1 for opt in ['install', 'remove', 'update', 'query', 'build', 'clean', 'migrate'] if getattr(self, opt)]) if count != 1: raise ExceptionNoStackTraceNeeded('Exactly one of the following must be provided: --install, --remove, -update, --query, --clean, --migrate') if self.query: # gppkg -q can be supplemented with --info, --list, --all count = sum([1 for opt in ['info', 'list', 'all'] if options.__dict__[opt]]) if count > 1: raise ExceptionNoStackTraceNeeded('For --query, at most one of the following can be provided: --info, --list, --all') # for all query options other than --all, a package path must be provided if not options.all and len(args) != 1: raise ExceptionNoStackTraceNeeded('A package must be specified for -q, -q --info, and -q --list.') if options.info: self.query = (QueryPackage.INFO, args[0]) elif options.list: self.query = (QueryPackage.LIST, args[0]) elif options.all: self.query = (QueryPackage.ALL, None) else: self.query = (None, args[0]) elif self.migrate: if len(args) != 2: raise ExceptionNoStackTraceNeeded('Invalid syntax, expecting "gppkg --migrate <from_gphome> <to_gphome>".') self.migrate = (args[0], args[1]) # gppkg should check gpexpand status unless in build mode. # # Build mode does not use any information from the cluster and does not # affect its running status, in fact it does not require a cluster # exists at all. if not self.build: check_result, msg = gp.conflict_with_gpexpand("gppkg", refuse_phase1=True, refuse_phase2=False) if not check_result: raise ExceptionNoStackTraceNeeded(msg)
def validate(failed, live, failover): checkNotNone("liveSegment", live) if failed is None and failover is None: raise Exception( "internal error: insufficient information to recover a mirror") if not live.isSegmentQE(): raise ExceptionNoStackTraceNeeded( "Segment to recover from for content %s is not a correct segment " "(it is a coordinator or standby coordinator)" % live.getSegmentContentId()) if not live.isSegmentPrimary(True): raise ExceptionNoStackTraceNeeded( "Segment to recover from for content %s is not a primary" % live.getSegmentContentId()) if not live.isSegmentUp(): raise ExceptionNoStackTraceNeeded( "Primary segment is not up for content %s" % live.getSegmentContentId()) if live.unreachable: raise ExceptionNoStackTraceNeeded( "The recovery source segment %s (content %s) is unreachable." % (live.getSegmentHostName(), live.getSegmentContentId())) if failed is not None: if failed.getSegmentContentId() != live.getSegmentContentId(): raise ExceptionNoStackTraceNeeded( "The primary is not of the same content as the failed mirror. Primary content %d, " "mirror content %d" % (live.getSegmentContentId(), failed.getSegmentContentId())) if failed.getSegmentDbId() == live.getSegmentDbId(): raise ExceptionNoStackTraceNeeded( "For content %d, the dbid values are the same. " "A segment may not be recovered from itself" % live.getSegmentDbId()) if failover is not None: if failover.getSegmentContentId() != live.getSegmentContentId(): raise ExceptionNoStackTraceNeeded( "The primary is not of the same content as the mirror. Primary content %d, " "mirror content %d" % (live.getSegmentContentId(), failover.getSegmentContentId())) if failover.getSegmentDbId() == live.getSegmentDbId(): raise ExceptionNoStackTraceNeeded( "For content %d, the dbid values are the same. " "A segment may not be built from itself" % live.getSegmentDbId()) if failover.unreachable: raise ExceptionNoStackTraceNeeded( "The recovery target segment %s (content %s) is unreachable." % (failover.getSegmentHostName(), failover.getSegmentContentId())) if failed is not None and failover is not None: # for now, we require the code to have produced this -- even when moving the segment to another # location, we preserve the directory assert failed.getSegmentDbId() == failover.getSegmentDbId()
def validate_options(self): if self.table_file is None: raise ExceptionNoStackTraceNeeded('Please specify table file') if not os.path.exists(self.table_file): raise ExceptionNoStackTraceNeeded('Unable to find table file "{table_file}"'.format(table_file=self.table_file)) if self.database is None: raise ExceptionNoStackTraceNeeded('Please specify the correct database') if self.port is None: if 'PGPORT' not in os.environ: raise ExceptionNoStackTraceNeeded('Please specify PGPORT using -p option or set PGPORT in the environment') self.port = os.environ['PGPORT']
def _process_createdb(self, restore_timestamp, restore_db, master_datadir, master_port): conn = None try: dburl = dbconn.DbURL(port=master_port) conn = dbconn.connect(dburl) count = execSQLForSingleton(conn, "select count(*) from pg_database where datname='%s';" % restore_db) if count == 1: logger.info("Dropping database %s" % restore_db) try: cursor=conn.cursor() cursor.execute("commit") # hack to move drop stmt out of implied transaction cursor.execute("drop database %s" % restore_db) cursor.close() except Exception, e: logger.exception("Could not create database %s" % restore_db) raise ExceptionNoStackTraceNeeded('Failed to drop database %s' % restore_db) else: logger.info('Dropped database %s' % restore_db) finally: if conn is not None: conn.close() createdb_file = os.path.join(master_datadir, DUMP_DIR, restore_timestamp[0:8], "%s%s" % (CREATEDB_PREFIX, restore_timestamp)) logger.info('Invoking %s' % createdb_file) Psql('Invoking schema dump', filename=createdb_file).run(validateAfter=True)
def execute(self): gparray = GpArray.initFromCatalog(dbconn.DbURL(port=self.master_port), utility=True) from_host, from_path = self.host, self.path logger.info("Commencing remote database dump file recovery process, please wait...") segs = [seg for seg in gparray.getDbList() if seg.isSegmentPrimary(current_role=True) or seg.isSegmentMaster()] pool = WorkerPool(numWorkers = min(len(segs), self.batch_default)) for seg in segs: if seg.isSegmentMaster(): file = '%s%s' % (MASTER_DBDUMP_PREFIX, self.restore_timestamp) else: file = '%s0_%d_%s' % (DBDUMP_PREFIX, seg.getSegmentDbId(), self.restore_timestamp) if self.compress: file += '.gz' to_host = seg.getSegmentHostName() to_path = os.path.join(seg.getSegmentDataDirectory(), DUMP_DIR, self.restore_timestamp[0:8]) if not CheckRemoteDir(to_path, to_host).run(): logger.info('Creating directory %s on %s' % (to_path, to_host)) try: MakeRemoteDir(to_path, to_host).run() except OSError, e: raise ExceptionNoStackTraceNeeded("Failed to create directory %s on %s" % (to_path, to_host)) logger.info("Commencing remote copy from %s to %s:%s" % (from_host, to_host, to_path)) pool.addCommand(Scp('Copying dump for seg %d' % seg.getSegmentDbId(), srcFile=os.path.join(from_path, file), dstFile=os.path.join(to_path, file), srcHost=from_host, dstHost=to_host))
def parse_gpexpand_segment_line(filename, lineno, line): """ Parse a line of the gpexpand configuration file. >>> parse_gpexpand_segment_line('file', 1, "localhost:[::1]:40001:/Users/ctaylor/data/p2/gpseg1:4:1:p") ('localhost', '::1', '40001', '/Users/ctaylor/data/p2/gpseg1', '4', '1', 'p', None) >>> parse_gpexpand_segment_line('file', 1, "localhost:[::1]:40001:/Users/ctaylor/data/p2/gpseg1:4:1:p:41001") ('localhost', '::1', '40001', '/Users/ctaylor/data/p2/gpseg1', '4', '1', 'p', '41001') """ p = LineParser(caller(), filename, lineno, line) hostname = p.handle_field( '[host]') # [host] indicates possible IPv6 address address = p.handle_field( '[address]') # [address] indicates possible IPv6 address port = p.handle_field('port') datadir = p.handle_field('datadir') dbid = p.handle_field('dbid') contentId = p.handle_field('contentId') role = p.handle_field('role') replicationPort = None if p.rest is not None: replicationPort = p.handle_field('replicationPort') if p.rest is not None: msg = "unexpected characters after replicationPort >>%s" % p.rest raise ExceptionNoStackTraceNeeded( "%s:%s:%s LINE >>%s\n%s" % (filename, lineno, caller(), line, msg)) return hostname, address, port, datadir, dbid, contentId, role, replicationPort
def execute(self): if ((len(self.include_dump_tables) > 0 or (self.include_dump_tables_file is not None)) and (len(self.exclude_dump_tables) > 0 or (self.exclude_dump_tables_file is not None))): raise ExceptionNoStackTraceNeeded( "Cannot use -t/--table-file and -T/--exclude-table-file options at same time" ) elif len(self.include_dump_tables ) > 0 or self.include_dump_tables_file is not None: logger.info("Configuring for single-database, include-table dump") ValidateIncludeTargets( dump_database=self.dump_database, dump_schema=self.dump_schema, include_dump_tables=self.include_dump_tables, include_dump_tables_file=self.include_dump_tables_file, master_port=self.master_port).run() elif len(self.exclude_dump_tables ) > 0 or self.exclude_dump_tables_file is not None: logger.info("Configuring for single-database, exclude-table dump") self.exclude_dump_tables = ValidateExcludeTargets( dump_database=self.dump_database, dump_schema=self.dump_schema, exclude_dump_tables=self.exclude_dump_tables, exclude_dump_tables_file=self.exclude_dump_tables_file, master_port=self.master_port).run() else: logger.info("Configuring for single database dump") return self.exclude_dump_tables
def parse_gprecoverseg_line(filename, lineno, line): """ Parse a line in the gprecoverseg configuration file other than the first. >>> line = "[::1]:40001:/Users/ctaylor/data/m2/gpseg1" >>> fixed, flex = parse_gprecoverseg_line('file', 1, line) >>> fixed["failedAddress"], fixed["failedPort"], fixed["failedDataDirectory"] ('::1', '40001', '/Users/ctaylor/data/m2/gpseg1') """ groups = len(line.split()) if groups not in [1, 2]: msg = "only one or two groups of fields delimited by a space, not %d" % groups raise ExceptionNoStackTraceNeeded("%s:%s:%s LINE >>%s\n%s" % (filename, lineno, caller(), line, msg)) fixed = {} flexible = {} p = LineParser(caller(), filename, lineno, line) p.handle_field('[failedAddress]', fixed) # [failedAddress] indicates possible IPv6 address p.handle_field('failedPort', fixed) if groups == 1: p.handle_field('failedDataDirectory', fixed) else: p.handle_field('failedDataDirectory', fixed, delimiter=' ', stripchars=' \t') # MPP-15675 note stripchars here and next line p.handle_field('[newAddress]', fixed, stripchars=' \t') # [newAddress] indicates possible IPv6 address p.handle_field('newPort', fixed) p.handle_field('newDataDirectory', fixed) return fixed, flexible
def execute(self): existing_tables = [] table_counts = [] conn = None try: dburl = dbconn.DbURL(port=self.master_port, dbname=self.restore_db) conn = dbconn.connect(dburl) for restore_table in self.restore_tables: if '.' not in restore_table: logger.warn("No schema name supplied for %s, removing from list of tables to restore" % restore_table) continue schema, table = restore_table.split('.') count = execSQLForSingleton(conn, "select count(*) from pg_class, pg_namespace where pg_class.relname = '%s' and pg_class.relnamespace = pg_namespace.oid and pg_namespace.nspname = '%s'" % (table, schema)) if count == 0: logger.warn("Table %s does not exist in database %s, removing from list of tables to restore" % (table, self.restore_db)) continue count = execSQLForSingleton(conn, "select count(*) from %s.%s" % (schema, table)) if count > 0: logger.warn('Table %s has %d records %s' % (restore_table, count, WARN_MARK)) existing_tables.append(restore_table) table_counts.append((restore_table, count)) finally: if conn is not None: conn.close() if len(existing_tables) == 0: raise ExceptionNoStackTraceNeeded("Have no tables to restore") logger.info("Have %d tables to restore, will continue" % len(existing_tables)) return (existing_tables, table_counts)
def ensure_more_to_process(self, name): "Raise an exception if we've exhausted the input line" if self.rest is None: msg = "out of values (reading %s)" % name raise ExceptionNoStackTraceNeeded( "%s:%s:%s LINE >>%s\n%s" % (self.filename, self.lineno, self.caller, self.line, msg))
def ensure_starts_with(self, expected): "Returns true if line starts with expected value, or raise an exception otherwise" if not self.line.startswith(expected): msg = "does not start with %s" % expected raise ExceptionNoStackTraceNeeded( "%s:%s:%s LINE >>%s\n%s" % (self.filename, self.lineno, self.caller, self.line, msg)) self.rest = self.rest[len(expected):]
def check_values(lineno, address=None, port=None, datadir=None, content=None, hostname=None, dbid=None, role=None): if address is not None and not is_valid_address(address): raise ExceptionNoStackTraceNeeded("Invalid address on line %d" % lineno) if port is not None and not is_valid_port(port): raise ExceptionNoStackTraceNeeded("Invalid port on line %d" % lineno) if datadir is not None and not is_valid_datadir(datadir): raise ExceptionNoStackTraceNeeded("Invalid directory on line %d" % lineno) if content is not None and not is_valid_contentid(content): raise ExceptionNoStackTraceNeeded("Invalid content ID on line %d" % lineno) if hostname is not None and not is_valid_address(hostname): raise ExceptionNoStackTraceNeeded("Invalid hostname on line %d" % lineno) if dbid is not None and not is_valid_dbid(dbid): raise ExceptionNoStackTraceNeeded("Invalid dbid on line %d" % lineno) if role is not None and not is_valid_role(role): raise ExceptionNoStackTraceNeeded("Invalid role on line %d" % lineno)
def run_sql(conn, query): try: cursor = dbconn.execSQL(conn, query) res = cursor.fetchall() conn.commit() cursor.close() except Exception, db_err: raise ExceptionNoStackTraceNeeded("%s" % db_err.__str__()) # .split('\n')[0])
def execute(self): rebuild_excludes = [] dump_tables = [] for dump_table in self.exclude_dump_tables: dump_tables.append(dump_table) if self.exclude_dump_tables_file is not None: exclude_file = open(self.exclude_dump_tables_file, 'rU') if not exclude_file: raise ExceptionNoStackTraceNeeded("Can't open file %s" % exclude_dump_tables_file) for line in exclude_file: dump_tables.append(line.strip('\n')) exclude_file.close() for dump_table in dump_tables: if '.' not in dump_table: raise ExceptionNoStackTraceNeeded( "No schema name supplied for exclude table %s" % dump_table) schema, table = dump_table.split('.') exists = CheckTableExists(schema=schema, table=table, database=self.dump_database, master_port=self.master_port).run() if exists: if self.dump_schema != schema: logger.info("Adding table %s to exclude list" % dump_table) rebuild_excludes.append(dump_table) else: logger.warn( "Schema dump request and exclude table %s not in that schema, ignoring" % dump_table) else: logger.warn( "Exclude table %s does not exist in %s database, ignoring" % (dump_table, self.dump_database)) if len(rebuild_excludes) == 0: logger.warn( "All exclude table names have been removed due to issues, see log file" ) return self.exclude_dump_tables
def execute(self): if self.backup_dir is not None and self.report_dir is not None: master_dirs_to_check = [self.backup_dir, self.report_dir] elif self.backup_dir is not None: master_dirs_to_check = [self.backup_dir, self.master_datadir] elif self.report_dir is not None: master_dirs_to_check = [self.report_dir, self.master_datadir] else: master_dirs_to_check = [self.master_datadir] for dir in master_dirs_to_check: try: ValidateDumpDirs(dir).run() except DumpDirCreateFailed, e: raise ExceptionNoStackTraceNeeded( 'Could not create %s on master. Cannot continue.' % dir) except DumpDirNotWritable, e: raise ExceptionNoStackTraceNeeded( 'Could not write to %s on master. Cannot continue.' % dir)
def validate_table(self, schema_name, table_name): with dbconn.connect(dbconn.DbURL(dbname=self.database, port=self.port)) as conn: c = execSQLForSingleton(conn, """SELECT count(*) FROM pg_class, pg_namespace WHERE pg_namespace.nspname = '{schema}' AND pg_class.relname = '{table}'""".format(schema=schema_name, table=table_name)) if not c: raise ExceptionNoStackTraceNeeded('Table {schema}.{table} does not exist' .format(schema=schema_name, table=table_name))
def execute(self): for fake_time in range(0, 1000000): fake_timestamp = "%s%06d" % (self.restore_timestamp[0:8], fake_time) path = os.path.join(self.master_datadir, DUMP_DIR, fake_timestamp[0:8], "%s%s" % (MASTER_DBDUMP_PREFIX, fake_timestamp)) if self.compress: path += '.gz' if not CheckFile(path).run(): break else: raise ExceptionNoStackTraceNeeded("Could not construct table dump") return fake_timestamp
def validate_table_file(self): table_list = [] with open(self.table_file) as fp: for line in fp: line = line.strip() try: schema_name, table_name, sort_column_list = self.parse_line(line) except Exception as e: raise ExceptionNoStackTraceNeeded("Line '{line}' is not formatted correctly: {ex}".format(line=line, ex=e)) table_list.append((schema_name, table_name, sort_column_list)) return table_list
def execute(self): path = os.path.join(self.master_datadir, DUMP_DIR, self.candidate_timestamp[0:8]) createdb_file = os.path.join(path, "%s%s" % (CREATEDB_PREFIX, self.candidate_timestamp)) if not CheckFile(createdb_file).run(): raise ExceptionNoStackTraceNeeded("Dump file %s%s does not exist on Master" % (CREATEDB_PREFIX, self.candidate_timestamp)) restore_db = GetDbName(createdb_file).run() compressed_file = os.path.join(path, "%s%s.gz" % (MASTER_DBDUMP_PREFIX, self.candidate_timestamp)) compress = CheckFile(compressed_file).run() return (self.candidate_timestamp, restore_db, compress)
def run(self): if self.__options.parallelDegree < 1 or self.__options.parallelDegree > 64: raise ProgramArgumentValidationException( "Invalid parallelDegree provided with -B argument: %d" % self.__options.parallelDegree) self.__pool = WorkerPool(self.__options.parallelDegree) gpEnv = GpMasterEnvironment(self.__options.masterDataDirectory, True) # verify "where to recover" options optionCnt = 0 if self.__options.newRecoverHosts is not None: optionCnt += 1 if self.__options.recoveryConfigFile is not None: optionCnt += 1 if self.__options.rebalanceSegments: optionCnt += 1 if optionCnt > 1: raise ProgramArgumentValidationException("Only one of -i, -p, and -r may be specified") faultProberInterface.getFaultProber().initializeProber(gpEnv.getMasterPort()) confProvider = configInterface.getConfigurationProvider().initializeProvider(gpEnv.getMasterPort()) gpArray = confProvider.loadSystemConfig(useUtilityMode=False) num_workers = min(len(gpArray.get_hostlist()), self.__options.parallelDegree) hosts = set(gpArray.get_hostlist(includeMaster=False)) unreachable_hosts = get_unreachable_segment_hosts(hosts, num_workers) for i, segmentPair in enumerate(gpArray.segmentPairs): if segmentPair.primaryDB.getSegmentHostName() in unreachable_hosts: logger.warning("Not recovering segment %d because %s is unreachable" % (segmentPair.primaryDB.dbid, segmentPair.primaryDB.getSegmentHostName())) gpArray.segmentPairs[i].primaryDB.unreachable = True if segmentPair.mirrorDB.getSegmentHostName() in unreachable_hosts: logger.warning("Not recovering segment %d because %s is unreachable" % (segmentPair.mirrorDB.dbid, segmentPair.mirrorDB.getSegmentHostName())) gpArray.segmentPairs[i].mirrorDB.unreachable = True if not gpArray.hasMirrors: raise ExceptionNoStackTraceNeeded( 'GPDB Mirroring replication is not configured for this Greenplum Database instance.') # We have phys-rep/filerep mirrors. if self.__options.newRecoverHosts is not None: try: uniqueHosts = [] for h in self.__options.newRecoverHosts.split(','): if h.strip() not in uniqueHosts: uniqueHosts.append(h.strip()) self.__options.newRecoverHosts = uniqueHosts except Exception, ex: raise ProgramArgumentValidationException( \ "Invalid value for recover hosts: %s" % ex)
def execute(self): gparray = GpArray.initFromCatalog(dbconn.DbURL(port=self.master_port), utility=True) failed_segs = [ seg for seg in gparray.getDbList() if seg.isSegmentPrimary(current_role=True) and seg.isSegmentDown() ] if len(failed_segs) != 0: logger.warn("Failed primary segment instances detected") failed_dbids = [seg.getSegmentDbid() for seg in failed_segs] raise ExceptionNoStackTraceNeeded( "Detected failed segment(s) with dbid=%s" % ",".join(failed_dbids))
def _validate(rows): """ Runs checks for making sure all the rows are consistent :param rows: :return: """ failed = {} new = {} for row in rows: address, port, datadir, lineno = \ row['failedAddress'], row['failedPort'], row['failedDataDirectory'], row['lineno'] if address + datadir in failed: msg = 'config file lines {0} and {1} conflict: ' \ 'Cannot recover the same failed segment {2} and data directory {3} twice.' \ .format(failed[address+datadir], lineno, address, datadir) raise ExceptionNoStackTraceNeeded(msg) failed[address + datadir] = lineno if 'newAddress' not in row: if address + datadir in new: msg = 'config file lines {0} and {1} conflict: ' \ 'Cannot recover segment {2} with data directory {3} in place if it is used as a recovery segment.' \ .format(new[address+datadir], lineno, address, datadir) raise ExceptionNoStackTraceNeeded(msg) continue address2, port2, datadir2 = row['newAddress'], row['newPort'], row[ 'newDataDirectory'] if address2 + datadir2 in new: msg = 'config file lines {0} and {1} conflict: ' \ 'Cannot recover to the same segment {2} and data directory {3} twice.' \ .format(new[address2+datadir2], lineno, address2, datadir2) raise ExceptionNoStackTraceNeeded(msg) new[address2 + datadir2] = lineno
class ValidateDiskSpace(Operation): # TODO: this doesn't take into account that multiple segments may be dumping to the same logical disk. def __init__(self, free_space_percent, compress, dump_database, include_dump_tables, batch_default, master_port): self.free_space_percent = free_space_percent self.compress = compress self.dump_database = dump_database self.include_dump_tables = include_dump_tables self.batch_default = batch_default self.master_port = master_port def execute(self): ValidateGpToolkit(database=self.dump_database, master_port=self.master_port).run() operations = [] gparray = GpArray.initFromCatalog(dbconn.DbURL(port=self.master_port), utility=True) segs = [ seg for seg in gparray.getDbList() if seg.isSegmentPrimary(current_role=True) ] for seg in segs: operations.append( RemoteOperation( ValidateSegDiskSpace( free_space_percent=self.free_space_percent, compress=self.compress, dump_database=self.dump_database, include_dump_tables=self.include_dump_tables, datadir=seg.getSegmentDataDirectory(), segport=seg.getSegmentPort()), seg.getSegmentHostName())) ParallelOperation(operations, self.batch_default).run() success = 0 for remote in operations: host = remote.host try: remote.get_ret() except NotEnoughDiskSpace, e: logger.error( "%s has insufficient disk space. [Need: %dK, Free %dK]" % (host, e.needed_space, e.free_space)) else: success += 1 if success < len(operations): raise ExceptionNoStackTraceNeeded( "Cannot continue. %d segment(s) failed disk space checks" % (len(operations) - success))
def execute(self): conn = None try: dburl = dbconn.DbURL(port=self.master_port) conn = dbconn.connect(dburl) count = execSQLForSingleton( conn, "select count(*) from pg_database where datname='%s';" % self.database) if count == 0: raise ExceptionNoStackTraceNeeded( "Database %s does not exist." % self.database) finally: if conn is not None: conn.close()