示例#1
0
def _stop_slave(client, config):
    """Stop MySQL replication"""
    try:
        client.stop_slave(sql_thread_only=True)
        LOG.info("Stopped slave")
        config['mysql:replication'] = {}
        repl_cfg = config['mysql:replication']
    except MySQLError as exc:
        raise BackupError("Failed to stop slave[%d]: %s" % exc.args)

    try:
        slave_info = client.show_slave_status()
        if slave_info:
            # update config with replication info
            log_file = slave_info['relay_master_log_file']
            log_pos = slave_info['exec_master_log_pos']
            repl_cfg['slave_master_log_file'] = log_file
            repl_cfg['slave_master_log_pos'] = log_pos
    except MySQLError as exc:
        raise BackupError("Failed to acquire slave status[%d]: %s" % \
                            exc.args)
    try:
        master_info = client.show_master_status()
        if master_info:
            repl_cfg['master_log_file'] = master_info['file']
            repl_cfg['master_log_pos'] = master_info['position']
    except MySQLError as exc:
        raise BackupError("Failed to acquire master status [%d] %s" % exc.args)

    LOG.info("MySQL Replication has been stopped.")
示例#2
0
    def check(self):
        LOG.info("Checking that SQLite backups can run.")
        if not os.path.exists(self.sqlite_bin):
            raise BackupError("SQLite binary [%s] doesn't exist!" %
                              self.sqlite_bin)

        for db in self.config['sqlite']['databases']:
            # sometimes picks up empty string ('')
            if not db:
                continue

            path = os.path.abspath(os.path.expanduser(db))
            if not os.path.exists(path):
                LOG.error("SQLite database [%s] doesn't exist!" % path)
                self.invalid_databases.append(db)
                continue

            process = Popen([self.sqlite_bin, path, '.schema'],
                            stdin=open('/dev/null', 'r'),
                            stdout=open('/dev/null', 'w'),
                            stderr=PIPE)
            _, stderroutput = process.communicate()

            if process.returncode != 0:
                LOG.error(stderroutput)
                self.invalid_databases.append(db)
            else:
                self.databases.append(db)

        if len(self.databases) == 0 and len(self.invalid_databases) == 0:
            raise BackupError("No SQLite databases to backup!")
示例#3
0
    def estimate_backup_size(self):
        """Estimate the size of the backup this plugin will generate"""

        LOG.info("Estimating size of mysqldump backup")
        estimate_method = self.config['mysqldump']['estimate-method']

        if estimate_method.startswith('const:'):
            try:
                return parse_size(estimate_method[6:])
            except ValueError as exc:
                raise BackupError(str(exc))

        if estimate_method != 'plugin':
            raise BackupError("Invalid estimate-method '%s'" % estimate_method)

        try:
            db_iter = DatabaseIterator(self.client)
            tbl_iter = MetadataTableIterator(self.client)
            try:
                self.client.connect()
            except Exception as ex:
                LOG.error("Failed to connect to database")
                LOG.error("%s", ex)
                raise BackupError("MySQL Error %s" % ex)
            try:
                self.schema.refresh(db_iter=db_iter, tbl_iter=tbl_iter)
            except MySQLError as exc:
                LOG.error("Failed to estimate backup size")
                LOG.error("[%d] %s", *exc.args)
                raise BackupError("MySQL Error [%d] %s" % exc.args)
            return float(sum([db.size for db in self.schema.databases]))
        finally:
            self.client.disconnect()
示例#4
0
文件: tar.py 项目: soulen3/holland
    def backup(self):
        if self.dry_run:
            return
        if not os.path.exists(self.config['tar']['directory']) \
         or not os.path.isdir(self.config['tar']['directory']):
            raise BackupError('{0} is not a directory!'.format(self.config['tar']['directory']))
        out_name = "{0}.tar".format(
            self.config['tar']['directory'].lstrip('/').replace('/', '_'))
        outfile = os.path.join(self.target_directory, out_name)
        args = ['tar', 'c', self.config['tar']['directory']]
        errlog = TemporaryFile()
        stream = self._open_stream(outfile, 'w')
        LOG.info("Executing: %s", list2cmdline(args))
        pid = Popen(
            args,
            stdout=stream.fileno(),
            stderr=errlog.fileno(),
            close_fds=True)
        status = pid.wait()
        try:
            errlog.flush()
            errlog.seek(0)
            for line in errlog:
                LOG.error("%s[%d]: %s", list2cmdline(args), pid.pid, line.rstrip())
        finally:
            errlog.close()

        if status != 0:
            raise BackupError('tar failed (status={0})'.format(status))
示例#5
0
    def backup(self):
        """
        Start a backup.
        """

        # estimate and setup has completed at this point
        # so ensure the connection is closed - we will never reuse this
        self.connection.close()

        if self.dry_run:
            # Very simply dry run information
            # enough to know that:
            # 1) We can connect to Postgres using pgpass data
            # 2) The exact databases we would dump
            dry_run(self.databases, self.config)
            return

        # First run a pg_dumpall -g and save the globals
        # Then run a pg_dump for each database we find
        backup_dir = os.path.join(self.target_directory, 'data')

        # put everything in data/
        try:
            os.mkdir(backup_dir)
        except OSError as exc:
            raise BackupError("Failed to create backup directory %s" % backup_dir)

        try:
            backup_pgsql(backup_dir, self.config, self.databases)
        except (OSError, PgError) as exc:
            LOG.debug("Failed to backup Postgres. %s",
                          str(exc), exc_info=True)
            raise BackupError(str(exc))
示例#6
0
    def _backup(self):
        """Real backup method.  May raise BackupError exceptions"""
        config = self.config['mysqldump']
        # setup defaults_file with ignore-table exclusions
        defaults_file = os.path.join(self.target_directory, 'my.cnf')
        write_options(self.mysql_config, defaults_file)
        if config['exclude-invalid-views']:
            LOG.info("* Finding and excluding invalid views...")
            definitions_path = os.path.join(self.target_directory,
                                            'invalid_views.sql')
            exclude_invalid_views(self.schema, self.client, definitions_path)
        add_exclusions(self.schema, defaults_file)
        # find the path to the mysqldump command
        mysqldump_bin = find_mysqldump(path=config['mysql-binpath'])
        LOG.info("Using mysqldump executable: %s", mysqldump_bin)
        # setup the mysqldump environment
        extra_defaults = config['extra-defaults']
        try:
            mysqldump = MySQLDump(defaults_file,
                                  mysqldump_bin,
                                  extra_defaults=extra_defaults)
        except MySQLDumpError as exc:
            raise BackupError(str(exc))
        except Exception as ex:
            LOG.warning(ex)
        LOG.info("mysqldump version %s", '.'.join([str(digit)
                for digit in mysqldump.version]))
        options = collect_mysqldump_options(config, mysqldump, self.client)
        validate_mysqldump_options(mysqldump, options)

        os.mkdir(os.path.join(self.target_directory, 'backup_data'))

        if self.config['compression']['method'] != 'none' and \
            self.config['compression']['level'] > 0:
            try:
                cmd, ext = lookup_compression(self.config['compression']['method'])
            except OSError as exc:
                raise BackupError("Unable to load compression method '%s': %s" %
                                  (self.config['compression']['method'], exc))
            LOG.info("Using %s compression level %d with args %s",
                     self.config['compression']['method'],
                     self.config['compression']['level'],
                     self.config['compression']['options'])
        else:
            LOG.info("Not compressing mysqldump output")
            cmd = ''
            ext = ''

        try:
            start(mysqldump=mysqldump,
                  schema=self.schema,
                  lock_method=config['lock-method'],
                  file_per_database=config['file-per-database'],
                  open_stream=self._open_stream,
                  compression_ext=ext)
        except MySQLDumpError as exc:
            raise BackupError(str(exc))
示例#7
0
def start(mysqldump,
          schema=None,
          lock_method='auto-detect',
          file_per_database=True,
          open_stream=open,
          compression_ext=''):
    """Run a mysqldump backup"""

    if not schema and file_per_database:
        raise BackupError("file_per_database specified without a valid schema")

    if not schema:
        target_databases = ALL_DATABASES
    else:

        if len(schema.databases) == 0:
            raise BackupError("No databases found to backup")

        if not file_per_database and not [
                x for x in schema.excluded_databases
        ]:
            target_databases = ALL_DATABASES
        else:
            target_databases = [
                db for db in schema.databases if not db.excluded
            ]
            write_manifest(schema, open_stream, compression_ext)

    if file_per_database:
        flush_logs = '--flush-logs' in mysqldump.options
        if flush_logs:
            mysqldump.options.remove('--flush-logs')
        last = len(target_databases)
        for count, db in enumerate(target_databases):
            more_options = [mysqldump_lock_option(lock_method, [db])]
            # add --flush-logs only to the last mysqldump run
            if flush_logs and count == last:
                more_options.append('--flush-logs')
            db_name = encode(db.name)[0]
            if db_name != db.name:
                LOG.warning("Encoding file-name for database %s to %s",
                            db.name, db_name)
            try:
                stream = open_stream('%s.sql' % db_name, 'w')
            except (IOError, OSError), exc:
                raise BackupError("Failed to open output stream %s: %s" %
                                  ('%s.sql' + compression_ext, str(exc)))
            try:
                mysqldump.run([db.name], stream, more_options)
            finally:
                try:
                    stream.close()
                except (IOError, OSError), exc:
                    if exc.errno != errno.EPIPE:
                        LOG.error("%s", str(exc))
                        raise BackupError(str(exc))
示例#8
0
文件: plugin.py 项目: chder/holland
class MySQLDumpPlugin(object):
    """MySQLDump Backup Plugin interface for Holland"""
    CONFIGSPEC = CONFIGSPEC

    def __init__(self, name, config, target_directory, dry_run=False):
        self.name = name
        self.config = config
        self.target_directory = target_directory
        self.dry_run = dry_run
        self.config.validate_config(self.CONFIGSPEC)  # -> ValidationError

        # Setup a discovery shell to find schema items
        # This will iterate over items during the estimate
        # or backup phase, which will call schema.refresh()
        self.schema = MySQLSchema()
        config = self.config['mysqldump']
        self.schema.add_database_filter(include_glob(*config['databases']))
        self.schema.add_database_filter(
            exclude_glob(*config['exclude-databases']))

        self.schema.add_table_filter(include_glob_qualified(*config['tables']))
        self.schema.add_table_filter(
            exclude_glob_qualified(*config['exclude-tables']))
        self.schema.add_engine_filter(include_glob(*config['engines']))
        self.schema.add_engine_filter(exclude_glob(*config['exclude-engines']))

        self.mysql_config = build_mysql_config(self.config['mysql:client'])
        self.client = connect(self.mysql_config['client'])

    def estimate_backup_size(self):
        """Estimate the size of the backup this plugin will generate"""

        LOG.info("Estimating size of mysqldump backup")
        estimate_method = self.config['mysqldump']['estimate-method']

        if estimate_method.startswith('const:'):
            try:
                return parse_size(estimate_method[6:])
            except ValueError, exc:
                raise BackupError(str(exc))

        if estimate_method != 'plugin':
            raise BackupError("Invalid estimate-method '%s'" % estimate_method)

        try:
            db_iter = DatabaseIterator(self.client)
            tbl_iter = MetadataTableIterator(self.client)
            try:
                self.client.connect()
                self.schema.refresh(db_iter=db_iter, tbl_iter=tbl_iter)
            except MySQLError, exc:
                LOG.error("Failed to estimate backup size")
                LOG.error("[%d] %s", *exc.args)
                raise BackupError("MySQL Error [%d] %s" % exc.args)
            return float(sum([db.size for db in self.schema.databases]))
示例#9
0
    def backup(self):
        """Run a backup by running through a LVM snapshot against the device
        the MySQL datadir resides on
        """
        # connect to mysql and lookup what we're supposed to snapshot
        try:
            self.client.connect()
            datadir = os.path.realpath(self.client.show_variable('datadir'))
        except MySQLError as exc:
            raise BackupError("[%d] %s" % exc.args)

        LOG.info("Backing up %s via snapshot", datadir)
        # lookup the logical volume mysql's datadir sits on

        try:
            volume = LogicalVolume.lookup_from_fspath(datadir)
        except LookupError as exc:
            raise BackupError("Failed to lookup logical volume for %s: %s" %
                              (datadir, str(exc)))
        except Exception as ex:
            LOG.debug(ex)

        # create a snapshot manager
        snapshot = build_snapshot(self.config['mysql-lvm'], volume,
                                  suppress_tmpdir=self.dry_run)
        # calculate where the datadirectory on the snapshot will be located
        rpath = relpath(datadir, getmount(datadir))
        snap_datadir = os.path.abspath(os.path.join(snapshot.mountpoint, rpath))
        # setup actions to perform at each step of the snapshot process
        setup_actions(snapshot=snapshot,
                      config=self.config,
                      client=self.client,
                      snap_datadir=snap_datadir,
                      spooldir=self.target_directory)

        if self.dry_run:
            return self._dry_run(volume, snapshot, datadir)

        try:
            snapshot.start(volume)
        except CallbackFailuresError as exc:
            # XXX: one of our actions failed.  Log this better
            for callback, error in exc.errors:
                LOG.error("%s", error)
            raise BackupError("Error occurred during snapshot process. Aborting.")
        except LVMCommandError as exc:
            # Something failed in the snapshot process
            raise BackupError(str(exc))
        except Exception as ex:
            LOG.debug(ex)
示例#10
0
    def backup(self):
        """Run a database backup with xtrabackup"""
        defaults_file = os.path.join(self.target_directory,
                                     'my.xtrabackup.cnf')
        args = [
            self.config['xtrabackup']['innobackupex'],
            '--defaults-file=%s' % defaults_file,
            '--stream=tar4ibd',
            tempfile.gettempdir(),
        ]

        if self.config['xtrabackup']['slave-info']:
            args.insert(3, '--slave-info')
        if self.config['xtrabackup']['no-lock']:
            args.insert(2, '--no-lock')

        LOG.info("%s", list2cmdline(args))

        if self.dry_run:
            return

        config = build_mysql_config(self.config['mysql:client'])
        write_options(config, defaults_file)
        shutil.copyfileobj(
            open(self.config['xtrabackup']['global-defaults'], 'r'),
            open(defaults_file, 'a'))

        backup_path = os.path.join(self.target_directory, 'backup.tar')
        compression_stream = open_stream(backup_path, 'w',
                                         **self.config['compression'])
        error_log_path = os.path.join(self.target_directory, 'xtrabackup.log')
        error_log = open(error_log_path, 'wb')
        try:
            try:
                check_call(args,
                           stdout=compression_stream,
                           stderr=error_log,
                           close_fds=True)
            except OSError, exc:
                LOG.info("Command not found: %s", args[0])
                raise BackupError("%s not found. Is xtrabackup installed?" %
                                  args[0])
            except CalledProcessError, exc:
                LOG.info("%s failed", list2cmdline(exc.cmd))
                for line in open(error_log_path, 'r'):
                    if line.startswith('>>'):
                        continue
                    LOG.info("%s", line.rstrip())
                raise BackupError("%s failed" % exc.cmd[0])
示例#11
0
def build_snapshot(config, logical_volume, suppress_tmpdir=False):
    """Create a snapshot process for running through the various steps
    of creating, mounting, unmounting and removing a snapshot
    """
    snapshot_name = config['snapshot-name'] or \
                    logical_volume.lv_name + '_snapshot'
    extent_size = int(logical_volume.vg_extent_size)
    snapshot_size = config['snapshot-size']
    if not snapshot_size:
        snapshot_size = min(
            int(logical_volume.vg_free_count),
            (int(logical_volume.lv_size) * 0.2) / extent_size,
            (15 * 1024**3) / extent_size,
        )
        LOG.info("Auto-sizing snapshot-size to %s (%d extents)",
                 format_bytes(snapshot_size * extent_size), snapshot_size)
        if snapshot_size < 1:
            raise BackupError(
                "Insufficient free extents on %s "
                "to create snapshot (free extents = %s)" %
                (logical_volume.device_name(), logical_volume.vg_free_count))
    else:
        try:
            _snapshot_size = snapshot_size
            snapshot_size = parse_bytes(snapshot_size) / extent_size
            LOG.info(
                "Using requested snapshot-size %s "
                "rounded by extent-size %s to %s.", _snapshot_size,
                format_bytes(extent_size),
                format_bytes(snapshot_size * extent_size))
            if snapshot_size < 1:
                raise BackupError("Requested snapshot-size (%s) is "
                                  "less than 1 extent" % _snapshot_size)
            if snapshot_size > int(logical_volume.vg_free_count):
                LOG.info(
                    "Snapshot size requested %s, but only %s available.",
                    config['snapshot-size'],
                    format_bytes(int(logical_volume.vg_free_count) *
                                 extent_size,
                                 precision=4))
                LOG.info(
                    "Truncating snapshot-size to %d extents (%s)",
                    int(logical_volume.vg_free_count),
                    format_bytes(int(logical_volume.vg_free_count) *
                                 extent_size,
                                 precision=4))
                snapshot_size = int(logical_volume.vg_free_count)
        except ValueError, exc:
            raise BackupError("Problem parsing snapshot-size %s" % exc)
示例#12
0
def collect_mysqldump_options(config, mysqldump, client):
    """Do intelligent collection of mysqldump options from the config
    and add any additional options for further validation"""
    options = []
    if config['flush-logs']:
        options.append('--flush-logs')
    if config['flush-privileges']:
        if mysqldump.version < (5,0,26):
            LOG.warning("--flush privileges is available only for mysqldump "
                        "in 5.0.26+")
        else:
            options.append('--flush-privileges')
    if config['dump-routines']:
        if mysqldump.version < (5, 0, 13):
            LOG.warning("--routines is not available before mysqldump 5.0.13+")
        else:
            if mysqldump.version < (5, 0, 20):
                LOG.warning("mysqldump will not dump DEFINER values before "
                            "version 5.0.20.  You are running mysqldump from "
                            "version %s", mysqldump.version_str)
            options.append('--routines')
    if config['dump-events']:
        if mysqldump.version < (5, 1, 8):
            LOG.warning("--events only available for mysqldump 5.1.8+. skipping")
        else:
            options.append('--events')
    if config['max-allowed-packet']:
        options.append('--max-allowed-packet=' + config['max-allowed-packet'])
    if config['bin-log-position']:
        if client.show_variable('log_bin') != 'ON':
            raise BackupError("bin-log-position requested but "
                              "bin-log on server not active")
        options.append('--master-data=2')
    options.extend(config['additional-options'])
    return options
示例#13
0
文件: plugin.py 项目: chder/holland
    def backup(self):
        """Run a MySQL backup"""

        if self.schema.timestamp is None:
            self._fast_refresh_schema()

        mock_env = None
        if self.dry_run:
            mock_env = MockEnvironment()
            mock_env.replace_environment()
            LOG.info("Running in dry-run mode.")

        try:
            if self.config['mysqldump']['stop-slave']:
                self.client = connect(self.mysql_config['client'])
                if self.client.show_status('Slave_running',
                                           session=None) != 'ON':
                    raise BackupError(
                        "stop-slave enabled, but replication is "
                        "either not configured or the slave is not "
                        "running.")
                self.config.setdefault('mysql:replication', {})
                _stop_slave(self.client, self.config['mysql:replication'])
            self._backup()
        finally:
            if self.config['mysqldump']['stop-slave'] and \
                'mysql:replication' in self.config:
                _start_slave(self.client, self.config['mysql:replication'])
            if mock_env:
                mock_env.restore_environment()
示例#14
0
文件: plugin.py 项目: chder/holland
    def _backup(self):
        """Real backup method.  May raise BackupError exceptions"""
        config = self.config['mysqldump']

        # setup defaults_file with ignore-table exclusions
        defaults_file = os.path.join(self.target_directory, 'my.cnf')
        write_options(self.mysql_config, defaults_file)
        if config['exclude-invalid-views']:
            LOG.info("* Finding and excluding invalid views...")
            definitions_path = os.path.join(self.target_directory,
                                            'invalid_views.sql')
            exclude_invalid_views(self.schema, self.client, definitions_path)
        add_exclusions(self.schema, defaults_file)

        # find the path to the mysqldump command
        mysqldump_bin = find_mysqldump(path=config['mysql-binpath'])
        LOG.info("Using mysqldump executable: %s", mysqldump_bin)

        # setup the mysqldump environment
        extra_defaults = config['extra-defaults']
        try:
            mysqldump = MySQLDump(defaults_file,
                                  mysqldump_bin,
                                  extra_defaults=extra_defaults)
        except MySQLDumpError, exc:
            raise BackupError(str(exc))
示例#15
0
文件: plugin.py 项目: chder/holland
def _stop_slave(client, config=None):
    """Stop MySQL replication"""
    try:
        client.stop_slave(sql_thread_only=True)
        LOG.info("Stopped slave")
    except MySQLError, exc:
        raise BackupError("Failed to stop slave[%d]: %s" % exc.args)
示例#16
0
文件: innodb.py 项目: yodermk/holland
    def __call__(self, event, snapshot_fsm, snapshot):
        LOG.info("Starting InnoDB recovery")

        mysqld_exe = locate_mysqld_exe(self.mysqld_config)
        LOG.info("Bootstrapping with %s", mysqld_exe)

        mycnf_path = os.path.join(self.mysqld_config['datadir'],
                                  'my.innodb_recovery.cnf')
        self.mysqld_config['log-error'] = 'innodb_recovery.log'
        my_conf = generate_server_config(self.mysqld_config, mycnf_path)

        mysqld = MySQLServer(mysqld_exe, my_conf)
        mysqld.start(bootstrap=True)

        while mysqld.poll() is None:
            if signal.SIGINT in snapshot_fsm.sigmgr.pending:
                mysqld.kill(signal.SIGKILL)
            time.sleep(0.5)
        LOG.info("%s has stopped", mysqld_exe)

        if mysqld.returncode != 0:
            datadir = self.mysqld_config['datadir']
            for line in open(os.path.join(datadir, 'innodb_recovery.log'),
                             'r'):
                LOG.error("%s", line.rstrip())
            raise BackupError("%s exited with non-zero status (%s) during "
                              "InnoDB recovery" %
                              (mysqld_exe, mysqld.returncode))
        else:
            LOG.info("%s ran successfully", mysqld_exe)
示例#17
0
def _start_slave(client, config=None):
    """Start MySQL replication"""

    # Skip sanity check on slave coords if we didn't actually record any coords
    # This might happen if mysqld goes away between STOP SLAVE and SHOW SLAVE
    # STATUS.
    if config:
        try:
            slave_info = client.show_slave_status()
            if slave_info and slave_info['exec_master_log_pos'] != config['slave_master_log_pos']:
                LOG.warning("Sanity check on slave status failed.  "
                        "Previously recorded %s:%d but currently found %s:%d",
                        config['slave_master_log_file'], config['slave_master_log_pos'],
                        slave_info['relay_master_log_file'], slave_info['exec_master_log_pos'])
                LOG.warning("ALERT! Slave position changed during backup!")
        except MySQLError as exc:
            LOG.warning("Failed to sanity check replication[%d]: %s",
                             *exc.args)

        try:
            master_info = client.show_master_status()
            if master_info and master_info['position'] != config['master_log_pos']:
                LOG.warning("Sanity check on master status failed.  "
                        "Previously recorded %s:%s but currently found %s:%s",
                        config['master_log_file'], config['master_log_pos'],
                        master_info['file'], master_info['position'])
                LOG.warning("ALERT! Binary log position changed during backup!")
        except MySQLError as exc:
            LOG.warning("Failed to sanity check master status. [%d] %s", *exc.args)

    try:
        client.start_slave()
        LOG.info("Restarted slave")
    except MySQLError as exc:
        raise BackupError("Failed to restart slave [%d] %s" % exc.args)
示例#18
0
class DelphiniPlugin(object):
    """MySQL Cluster Backup Plugin implementation for Holland"""
    def __init__(self, name, config, target_directory, dry_run=False):
        config.validate_config(self.configspec())
        self.name = name
        self.config = config
        self.target_directory = target_directory
        self.dry_run = dry_run

    def estimate_backup_size(self):
        """Estimate the backup size"""
        # XXX: implement I_S querying or ssh du -sh perhaps
        return 0

    def backup(self):
        """Run a MySQL cluster backup"""
        config = self.config['mysql-cluster']
        dsn = config['connect-string']
        ssh_user = config['default-ssh-user']
        ssh_keyfile = config['default-ssh-keyfile']
        target_path = os.path.join(self.target_directory, 'data')
        try:
            os.mkdir(target_path)
        except OSError, exc:
            raise BackupError("Failed to create %s: %s", (target_path, exc))

        try:
            backup_id, stop_gcp = backup(dsn, ssh_user, ssh_keyfile,
                                         target_path)
        except ClusterError, exc:
            raise BackupError(exc)
示例#19
0
    def backup(self):
        """
        Start a backup.  This attempts one or more mysqldump
        runs.  On error, a BackupError exception will be thrown.
        """

        if self.config.lookup('mysqlhotcopy.stop-slave'):
            self.client.stop_slave()

        if self.config.lookup('mysqlhotcopy.bin-log-position'):
            #ensure mysql:replication section exists
            self.config.setdefault('mysql:replication', {})
            # Log slave data if we can:
            is_slave = self.client.is_slave_running()
            if is_slave:
                slave_status = self.client.show_slave_status()
                self.config['mysql:replication'][
                    'slave_master_log_file'] = slave_status[
                        'Relay_Master_Log_File']
                self.config['mysql:replication'][
                    'slave_master_log_pos'] = slave_status[
                        'Exec_Master_Log_Pos']
            master_data = self.client.show_master_status()
            if not master_data and not is_slave:
                LOG.error(
                    "bin-log-position requested, but this server is neither a master nor a slave"
                )
                raise BackupError("Failboat: replication not configured")
            self.config['mysql:replication']['master_log_file'] = master_data[
                0]
            self.config['mysql:replication']['master_log_pos'] = master_data[1]
            LOG.info("Writing master status to %s", self.mysql_config.path)
            if not self.dry_run:
                self.config.write()

        # trap exceptions so we make sure to restart the slave, if we stopped it
        # if the slave was already stopped, we will raise an exception when we
        # try to stop it (above)
        error = None
        try:
            self._backup()
        except Exception as ex:
            raise BackupError(ex)
        finally:
            if self.config.lookup('mysqlhotcopy.stop-slave'):
                self.client.start_slave()
示例#20
0
def backup(backupset_name, dry_run=False, skip_purge=False):

    # May raise a ConfigError if not backupset is found
    LOGGER.info("Loading config for backupset %s", backupset_name)
    try:
        backupset_cfg = load_backupset_config(backupset_name)
    except IOError, e:
        LOGGER.error("Failed to load backupset %s: %s", backupset_name, e)
        raise BackupError("Aborting due to previous errors.")
示例#21
0
 def _estimate_legacy_size(self, db):
     try:
         connection = get_connection(self.config, db)
         size = legacy_get_db_size(db, connection)
         connection.close()
         return size
     except dbapi.DatabaseError, exc:
         raise BackupError("Failed to estimate database size for %s: %s" %
                           (db, exc))
示例#22
0
文件: plugin.py 项目: chder/holland
def find_mysqldump(path=None):
    """Find a usable mysqldump binary in path or ENV[PATH]"""
    search_path = ':'.join(path) or os.environ.get('PATH', '')
    for _path in search_path.split(':'):
        if os.path.isfile(_path):
            return os.path.realpath(_path)
        if os.path.exists(os.path.join(_path, 'mysqldump')):
            return os.path.realpath(os.path.join(_path, 'mysqldump'))
    raise BackupError("Failed to find mysqldump in %s" % search_path)
示例#23
0
def verify_space(required_space, target_directory):
    available_space = disk_free(_find_existing_parent(target_directory))
    if required_space >= available_space:
        LOGGER.error("Insufficient Disk Space.  Required: %s Available: %s",
                     format_bytes(required_space),
                     format_bytes(available_space))
        raise BackupError("%s required but only %s available on %s" % \
                            (format_bytes(required_space),
                            format_bytes(available_space),
                            target_directory))
示例#24
0
 def estimate_backup_size(self):
     """Estimate the size of the backup this plugin will produce"""
     try:
         mysql_config = build_mysql_config(self.config['mysql:client'])
         client = connect(mysql_config['client'])
         datadir = client.show_variable('datadir')
         return directory_size(datadir)
     except MySQLError, exc:
         raise BackupError("Failed to lookup the MySQL datadir when "
                           "estimating backup size: [%d] %s" % exc.args)
示例#25
0
    def estimate_backup_size(self):
        """Estimate the size of the backup this plugin will generate"""
        LOG.info("Estimating size of mysqldump backup")
        estimate_method = self.config['mysqldump']['estimate-method']

        if estimate_method.startswith('const:'):
            try:
                return parse_size(estimate_method[6:])
            except ValueError, exc:
                raise BackupError(str(exc))
示例#26
0
文件: plugin.py 项目: chder/holland
 def backup(self):
     """Run a backup by running through a LVM snapshot against the device
     the MySQL datadir resides on
     """
     # connect to mysql and lookup what we're supposed to snapshot
     try:
         self.client.connect()
         datadir = os.path.realpath(self.client.show_variable('datadir'))
     except MySQLError, exc:
         raise BackupError("[%d] %s" % exc.args)
示例#27
0
文件: plugin.py 项目: chder/holland
    def estimate_backup_size(self):
        """Estimate the backup size this plugin will produce

        This is currently the total directory size of the MySQL datadir
        """
        try:
            self.client.connect()
            datadir = self.client.show_variable('datadir')
            self.client.disconnect()
        except MySQLError, exc:
            raise BackupError("[%d] %s" % exc.args)
示例#28
0
文件: tar.py 项目: soulen3/holland
    def __call__(self, event, snapshot_fsm, snapshot_vol):
        argv = [
            'tar', '--create', '--file', '-', '--verbose', '--totals',
            '--directory', self.snap_datadir, '.'
        ]

        pre_args = self.config['pre-args']
        if pre_args:
            LOG.info("Adding tar pre-args: %s", pre_args)
            pre_args = [
                arg.decode('utf8')
                for arg in shlex.split(pre_args.encode('utf8'))
            ]
            for option in pre_args:
                argv.insert(-3, option)
        for param in self.config['exclude']:
            argv.insert(-1, "--exclude")
            argv.insert(-1, os.path.join('.', param))
        post_args = self.config['post-args']
        if post_args:
            LOG.info("Adding tar post-args: %s", post_args)
            post_args = [
                arg.decode('utf8')
                for arg in shlex.split(post_args.encode('utf8'))
            ]
            for option in post_args:
                argv.append(option)
        LOG.info("Running: %s > %s", list2cmdline(argv),
                 self.archive_stream.name)

        archive_dirname = os.path.dirname(self.archive_stream.name)
        if pre_args or post_args:
            warning_readme = os.path.join(archive_dirname, "NONSTD_TAR.txt")
            warning_log = open('warning_readme', 'w')
            print >> warning_log, ("This tar file was generated with non-std "
                                   "args:")
            print >> warning_log, list2cmdline(argv)
        archive_log = os.path.join(archive_dirname, 'archive.log')
        process = Popen(argv,
                        preexec_fn=os.setsid,
                        stdout=self.archive_stream,
                        stderr=open(archive_log, 'w'),
                        close_fds=True)
        while process.poll() is None:
            if signal.SIGINT in snapshot_fsm.sigmgr.pending:
                os.kill(process.pid, signal.SIGKILL)
            time.sleep(0.5)

        try:
            self.archive_stream.close()
        except IOError, exc:
            LOG.error("tar output stream %s failed: %s",
                      self.archive_stream.name, exc)
            raise BackupError(str(exc))
示例#29
0
 def backup(self):
     """Run a MySQL cluster backup"""
     config = self.config['mysql-cluster']
     dsn = config['connect-string']
     ssh_user = config['default-ssh-user']
     ssh_keyfile = config['default-ssh-keyfile']
     target_path = os.path.join(self.target_directory, 'data')
     try:
         os.mkdir(target_path)
     except OSError, exc:
         raise BackupError("Failed to create %s: %s", (target_path, exc))
示例#30
0
def connect_simple(config):
    """Create a MySQLClientConnection given a mysql:client config
    section from a holland mysql backupset
    """
    try:
        mysql_config = build_mysql_config(config)
        LOG.debug("mysql_config => %r", mysql_config)
        connection = connect(mysql_config['client'], PassiveMySQLClient)
        connection.connect()
        return connection
    except MySQLError, exc:
        raise BackupError("[%d] %s" % exc.args)