Example #1
0
 def backup(self,
            name,
            snapshot_name,
            source,
            rbd,
            base_version_uid,
            block_size=None,
            tags=None):
     base_version_uid = VersionUid.create_from_readables(base_version_uid)
     benji_obj = None
     try:
         benji_obj = Benji(self.config, block_size=block_size)
         hints = None
         if rbd:
             data = ''.join(
                 [line for line in fileinput.input(rbd).readline()])
             hints = hints_from_rbd_diff(data)
         backup_version_uid = benji_obj.backup(name, snapshot_name, source,
                                               hints, base_version_uid,
                                               tags)
         if self.machine_output:
             benji_obj.export_any(
                 {'versions': benji_obj.ls(version_uid=backup_version_uid)},
                 sys.stdout,
                 ignore_relationships=[((Version, ), ('blocks', ))])
     finally:
         if benji_obj:
             benji_obj.close()
Example #2
0
    def generate_versions(self, testpath):
        version_uids = []
        old_size = 0
        init_database = True
        image_filename = os.path.join(testpath, 'image')
        for i in range(self.VERSIONS):
            logger.debug('Run {}'.format(i + 1))
            hints = []
            if old_size and random.randint(
                    0, 10
            ) == 0:  # every 10th time or so do not apply any changes.
                size = old_size
            else:
                size = 32 * 4 * kB + random.randint(-4 * kB, 4 * kB)
                old_size = size
                for j in range(random.randint(0, 10)):  # up to 10 changes
                    if random.randint(0, 1):
                        patch_size = random.randint(0, 4 * kB)
                        data = self.random_bytes(patch_size)
                        exists = "true"
                    else:
                        patch_size = random.randint(
                            0, 4 * 4 * kB)  # we want full blocks sometimes
                        data = b'\0' * patch_size
                        exists = "false"
                    offset = random.randint(0, size - 1 - patch_size)
                    logger.debug('Applied change at {}:{}, exists {}'.format(
                        offset, patch_size, exists))
                    self.patch(image_filename, offset, data)
                    hints.append({
                        'offset': offset,
                        'length': patch_size,
                        'exists': exists
                    })
            # truncate?
            if not os.path.exists(image_filename):
                open(image_filename, 'wb').close()
            with open(image_filename, 'r+b') as f:
                f.truncate(size)

            logger.debug('Applied {} changes, size is {}.'.format(
                len(hints), size))
            with open(os.path.join(testpath, 'hints'), 'w') as f:
                f.write(json.dumps(hints))

            benji_obj = self.benji_open(init_database=init_database)
            init_database = False
            with open(os.path.join(testpath, 'hints')) as hints:
                version = benji_obj.backup(
                    version_uid=VersionUid(str(uuid.uuid4())),
                    volume='data-backup',
                    snapshot='snapshot-name',
                    source='file:' + image_filename,
                    hints=hints_from_rbd_diff(hints.read()))
            version_uids.append((version.uid, size))
            benji_obj.close()
        return version_uids
Example #3
0
    def backup(self, version_name: str, snapshot_name: str, source: str,
               rbd_hints: str, base_version_uid: str, block_size: int,
               labels: List[str], storage) -> None:
        # Validate version_name and snapshot_name
        if not InputValidation.is_backup_name(version_name):
            raise benji.exception.UsageError(
                'Version name {} is invalid.'.format(version_name))
        if not InputValidation.is_snapshot_name(snapshot_name):
            raise benji.exception.UsageError(
                'Snapshot name {} is invalid.'.format(snapshot_name))
        base_version_uid_obj = VersionUid(
            base_version_uid) if base_version_uid else None
        if labels:
            label_add, label_remove = self._parse_labels(labels)
            if label_remove:
                raise benji.exception.UsageError(
                    'Wanting to delete labels on a new version is senseless.')
        benji_obj = None
        try:
            benji_obj = Benji(self.config, block_size=block_size)
            hints = None
            if rbd_hints:
                data = ''.join(
                    [line for line in fileinput.input(rbd_hints).readline()])
                hints = hints_from_rbd_diff(data)
            backup_version = benji_obj.backup(version_name, snapshot_name,
                                              source, hints,
                                              base_version_uid_obj, storage)

            if labels:
                for key, value in label_add:
                    benji_obj.add_label(backup_version.uid, key, value)
                for key in label_remove:
                    benji_obj.rm_label(backup_version.uid, key)
                if label_add:
                    logger.info('Added label(s) to version {}: {}.'.format(
                        backup_version.uid.v_string, ', '.join([
                            '{}={}'.format(name, value)
                            for name, value in label_add
                        ])))
                if label_remove:
                    logger.info('Removed label(s) from version {}: {}.'.format(
                        backup_version.uid.v_string, ', '.join(label_remove)))

            if self.machine_output:
                benji_obj.export_any({'versions': [backup_version]},
                                     sys.stdout,
                                     ignore_relationships=[((Version, ),
                                                            ('blocks', ))])
        finally:
            if benji_obj:
                benji_obj.close()
Example #4
0
    def backup(self, version_uid: str, volume: str, snapshot: str, source: str,
               rbd_hints: str, base_version_uid: str, block_size: int,
               labels: List[str], storage: str) -> None:
        if version_uid is None:
            version_uid = '{}-{}'.format(volume[:248], random_string(6))
        version_uid_obj = VersionUid(version_uid)
        base_version_uid_obj = VersionUid(
            base_version_uid) if base_version_uid else None

        if labels:
            label_add, label_remove = InputValidation.parse_and_validate_labels(
                labels)
        with Benji(self.config) as benji_obj:
            hints = None
            if rbd_hints:
                logger.debug(f'Loading RBD hints from file {rbd_hints}.')
                with open(rbd_hints, 'r') as f:
                    hints = hints_from_rbd_diff(f.read())
            backup_version = benji_obj.backup(
                version_uid=version_uid_obj,
                volume=volume,
                snapshot=snapshot,
                source=source,
                hints=hints,
                base_version_uid=base_version_uid_obj,
                storage_name=storage,
                block_size=block_size)

            if labels:
                for key, value in label_add:
                    benji_obj.add_label(backup_version.uid, key, value)
                for key in label_remove:
                    benji_obj.rm_label(backup_version.uid, key)
                if label_add:
                    logger.info('Added label(s) to version {}: {}.'.format(
                        backup_version.uid,
                        ', '.join('{}={}'.format(name, value)
                                  for name, value in label_add)))
                if label_remove:
                    logger.info('Removed label(s) from version {}: {}.'.format(
                        backup_version.uid, ', '.join(label_remove)))

            if self.machine_output:
                benji_obj.export_any({'versions': [backup_version]},
                                     sys.stdout,
                                     ignore_relationships=(((Version, ),
                                                            ('blocks', )), ))
Example #5
0
    def _backup(
        self, version_name: fields.Str(required=True),
        snapshot_name: fields.Str(required=True),
        source: fields.Str(required=True), rbd_hints: fields.Str(missing=None),
        base_version_uid: fields.Str(missing=None),
        block_size: fields.Int(missing=None), labels: fields.DelimitedList(
            fields.Str(), missing=None), storage_name: fields.Str(missing=None)
    ) -> str:
        # Validate version_name and snapshot_name
        if not InputValidation.is_backup_name(version_name):
            raise benji.exception.UsageError(
                'Version name {} is invalid.'.format(version_name))
        if not InputValidation.is_snapshot_name(snapshot_name):
            raise benji.exception.UsageError(
                'Snapshot name {} is invalid.'.format(snapshot_name))
        base_version_uid_obj = VersionUid(
            base_version_uid) if base_version_uid else None
        if labels:
            label_add, label_remove = self._parse_labels(labels)
        benji_obj = None
        try:
            benji_obj = Benji(self._config, block_size=block_size)
            hints = None
            if rbd_hints:
                with open(rbd_hints, 'r') as f:
                    hints = hints_from_rbd_diff(f.read())
            backup_version = benji_obj.backup(version_name, snapshot_name,
                                              source, hints,
                                              base_version_uid_obj,
                                              storage_name)

            result = StringIO()
            benji_obj.export_any({'versions': [backup_version]},
                                 result,
                                 ignore_relationships=[((Version, ),
                                                        ('blocks', ))])

            return result
        finally:
            if benji_obj:
                benji_obj.close()
Example #6
0
    def _api_v1_versions_create(
        self, version_uid: fields.Str(missing=None),
        volume: fields.Str(required=True), snapshot: fields.Str(required=True),
        source: fields.Str(required=True), rbd_hints: fields.Str(missing=None),
        base_version_uid: fields.Str(missing=None),
        block_size: fields.Int(missing=None),
        storage_name: fields.Str(missing=None)
    ) -> StringIO:
        if version_uid is None:
            version_uid = '{}-{}'.format(volume[:248], random_string(6))
        version_uid_obj = VersionUid(version_uid)
        base_version_uid_obj = VersionUid(
            base_version_uid) if base_version_uid else None

        result = StringIO()
        with Benji(self._config) as benji_obj:
            hints = None
            if rbd_hints:
                with open(rbd_hints, 'r') as f:
                    hints = hints_from_rbd_diff(f.read())
            backup_version = benji_obj.backup(
                version_uid=version_uid_obj,
                volume=volume,
                snapshot=snapshot,
                source=source,
                hints=hints,
                base_version_uid=base_version_uid_obj,
                storage_name=storage_name,
                block_size=block_size)

            benji_obj.export_any({'versions': [backup_version]},
                                 result,
                                 ignore_relationships=[((Version, ),
                                                        ('blocks', ))])

            response.status = 201
            response.set_header('Location',
                                f'{request.path}/{backup_version.uid}')
        return result
Example #7
0
    def test(self):
        testpath = self.testpath.path
        base_version_uid = None
        version_uids = []
        old_size = 0
        init_database = True
        image_filename = os.path.join(testpath, 'image')
        block_size = random.sample({512, 1024, 2048, 4096}, 1)[0]
        scrub_history = BlockUidHistory()
        deep_scrub_history = BlockUidHistory()
        storage_name = 's1'
        for i in range(1, 40):
            logger.debug('Run {}'.format(i + 1))
            hints = []
            if not os.path.exists(image_filename):
                open(image_filename, 'wb').close()
            if old_size and random.randint(0, 10) == 0:  # every 10th time or so do not apply any changes.
                size = old_size
            else:
                size = 32 * 4 * kB + random.randint(-4 * kB, 4 * kB)
                for j in range(random.randint(0, 10)):  # up to 10 changes
                    if random.randint(0, 1):
                        patch_size = random.randint(0, 4 * kB)
                        data = self.random_bytes(patch_size)
                        exists = "true"
                    else:
                        patch_size = random.randint(0, 4 * 4 * kB)  # we want full blocks sometimes
                        data = b'\0' * patch_size
                        exists = "false"
                    offset = random.randint(0, size - patch_size - 1)
                    logger.debug('Applied change at {}({}):{}, exists {}'.format(offset, int(offset / 4096), patch_size,
                                                                                 exists))
                    self.patch(image_filename, offset, data)
                    hints.append({'offset': offset, 'length': patch_size, 'exists': exists})

            # truncate?
            with open(image_filename, 'r+b') as f:
                f.truncate(size)

            if old_size and size > old_size:
                patch_size = size - old_size + 1
                offset = old_size - 1
                logger.debug('Image got bigger at {}({}):{}'.format(offset, int(offset / 4096), patch_size))
                hints.append({'offset': offset, 'length': patch_size, 'exists': 'true'})

            old_size = size

            copyfile(image_filename, '{}.{}'.format(image_filename, i + 1))

            logger.debug('Applied {} changes, size is {}.'.format(len(hints), size))
            with open(os.path.join(testpath, 'hints'), 'w') as f:
                f.write(json.dumps(hints))

            benji_obj = self.benjiOpen(init_database=init_database, block_size=block_size)
            init_database = False
            with open(os.path.join(testpath, 'hints')) as hints:
                version = benji_obj.backup(version_name='data-backup',
                                           version_snapshot_name='snapshot-name',
                                           source='file:' + image_filename,
                                           hints=hints_from_rbd_diff(hints.read()) if base_version_uid else None,
                                           base_version_uid=base_version_uid,
                                           storage_name=storage_name)
                # Don't keep a reference to version because we're closing the SQLAlchemy session
                version_uid = version.uid
            benji_obj.close()
            version_uids.append(version_uid)
            logger.debug('Backup successful')

            benji_obj = self.benjiOpen()
            benji_obj.add_label(version_uid, 'label-1', 'value-1')
            benji_obj.add_label(version_uid, 'label-2', 'value-2')
            benji_obj.close()
            logger.debug('Labeling of version successful')

            benji_obj = self.benjiOpen()
            benji_obj.rm(version_uid, force=True, keep_metadata_backup=True)
            benji_obj.close()
            logger.debug('Removal of version successful')

            benji_obj = self.benjiOpen()
            benji_obj.metadata_restore([version_uid], storage_name)
            benji_obj.close()
            logger.debug('Restore of version successful')

            benji_obj = self.benjiOpen()
            blocks = list(benji_obj._database_backend.get_blocks_by_version(version_uid))
            self.assertEqual(list(range(len(blocks))), sorted([block.id for block in blocks]))
            self.assertTrue(len(blocks) > 0)
            if len(blocks) > 1:
                self.assertTrue(reduce(and_, [block.size == block_size for block in blocks[:-1]]))
            benji_obj.close()
            logger.debug('Block list successful')

            benji_obj = self.benjiOpen()
            versions = benji_obj.ls()
            self.assertEqual(set(), set([version.uid for version in versions]) ^ set(version_uids))
            self.assertTrue(reduce(and_, [version.name == 'data-backup' for version in versions]))
            self.assertTrue(reduce(and_, [version.snapshot_name == 'snapshot-name' for version in versions]))
            self.assertTrue(reduce(and_, [version.block_size == block_size for version in versions]))
            self.assertTrue(reduce(and_, [version.size > 0 for version in versions]))
            benji_obj.close()
            logger.debug('Version list successful')

            benji_obj = self.benjiOpen()
            benji_obj.scrub(version_uid)
            benji_obj.close()
            logger.debug('Scrub successful')

            benji_obj = self.benjiOpen()
            benji_obj.deep_scrub(version_uid)
            benji_obj.close()
            logger.debug('Deep scrub successful')

            benji_obj = self.benjiOpen()
            benji_obj.deep_scrub(version_uid, 'file:' + image_filename)
            benji_obj.close()
            logger.debug('Deep scrub with source successful')

            benji_obj = self.benjiOpen()
            benji_obj.scrub(version_uid, history=scrub_history)
            benji_obj.close()
            logger.debug('Scrub with history successful')

            benji_obj = self.benjiOpen()
            benji_obj.deep_scrub(version_uid, history=deep_scrub_history)
            benji_obj.close()
            logger.debug('Deep scrub with history successful')

            benji_obj = self.benjiOpen()
            benji_obj.batch_scrub('uid == {}'.format(version_uid.integer), 100, 100)
            benji_obj.close()
            logger.debug('Batch scrub with history successful')

            benji_obj = self.benjiOpen()
            benji_obj.batch_deep_scrub('uid == {}'.format(version_uid.integer), 100, 100)
            benji_obj.close()
            logger.debug('Batch deep scrub with history successful')

            restore_filename = os.path.join(testpath, 'restore.{}'.format(i + 1))
            restore_filename_mdl = os.path.join(testpath, 'restore-mdl.{}'.format(i + 1))
            restore_filename_sparse = os.path.join(testpath, 'restore-sparse.{}'.format(i + 1))
            benji_obj = self.benjiOpen()
            benji_obj.restore(version_uid, 'file:' + restore_filename, sparse=False, force=False)
            benji_obj.close()
            self.assertTrue(self.same(image_filename, restore_filename))
            logger.debug('Restore successful')

            benji_obj = self.benjiOpen(in_memory_database=True)
            benji_obj.metadata_restore([version_uid], storage_name)
            benji_obj.restore(version_uid, 'file:' + restore_filename_mdl, sparse=False, force=False)
            benji_obj.close()
            self.assertTrue(self.same(image_filename, restore_filename_mdl))
            logger.debug('Database-less restore successful')

            benji_obj = self.benjiOpen(in_memory_database=True)
            benji_obj.metadata_restore([version_uid], storage_name)
            benji_obj.restore(version_uid, 'file:' + restore_filename_sparse, sparse=True, force=False)
            benji_obj.close()
            self.assertTrue(self.same(image_filename, restore_filename_sparse))
            logger.debug('Sparse restore successful')

            benji_obj = self.benjiOpen()
            objects_count, objects_size = benji_obj.storage_stats(storage_name)
            benji_obj.close()
            self.assertGreater(objects_count, 0)
            self.assertGreater(objects_size, 0)
            logger.debug(f'Storage stats: {objects_count} objects using {objects_size} bytes.')

            base_version_uid = version_uid

            # delete old versions
            if len(version_uids) > 10:
                benji_obj = self.benjiOpen()
                dismissed_versions = benji_obj.enforce_retention_policy('name=="data-backup"', 'latest10,hours24,days30')
                for dismissed_version in dismissed_versions:
                    version_uids.remove(dismissed_version.uid)
                benji_obj.close()

            if (i % 7) == 0:
                benji_obj = self.benjiOpen()
                benji_obj.cleanup(dt=0)
                benji_obj.close()
            if (i % 13) == 0:
                scrub_history = BlockUidHistory()
                deep_scrub_history = BlockUidHistory()
            if (i % 7) == 0:
                base_version_uid = None
                if storage_name == 's1':
                    storage_name = 's2'
                else:
                    storage_name = 's1'