def read_volume(name): _log = _logger.new(volume_name=name, ) env_name = name.replace('-', '_').upper() deltas_str = os.environ.get('VOLUME_{}_DELTAS'.format(env_name)) if not deltas_str: raise ConfigError( 'A volume {} was defined, but VOLUME_{}_DELTAS is not set'. format(name, env_name)) zone = os.environ.get('VOLUME_{}_ZONE'.format(env_name)) if not zone: raise ConfigError( 'A volume {} was defined, but VOLUME_{}_ZONE is not set'. format(name, env_name)) _log = _log.bind( deltas_str=deltas_str, zone=zone, ) rule = Rule( name=name, claim_name='', namespace='', deltas=parse_deltas(deltas_str), gce_disk=name, gce_disk_zone=zone, ) _log.info(events.Rule.ADDED_FROM_CONFIG, rule=rule) return rule
def setUp(self): self.mock_context = Context({}) self.rule = Rule(name='test_rule', deltas=[timedelta(hours=1), timedelta(days=30)], backend='test_backend', disk=self.TEST_DISK)
async def rule_from_snapshotrule( ctx: Context, resource: SnapshotRule ) -> Optional[Rule]: """This tries to build a rule within a `SnapshotRule` resource - the resource that we custom designed for this purpose. This is invoked whenever Kubernetes tells us that such a resource was created, deleted, or updated. There are two separate ways a `SnapshotRule` can be used: - A `SnapshotRule` resource can refer to a specific Cloud disk id to be snapshotted, e.g. 'example-disk' on 'gcloud'. This skips Kubernetes entirely. - A `SnapshotRule` resource can refer to a `PersistentVolumeClaim`. The disk this claim is bound to is the one we will snapshot. Rather than defining the snapshot interval etc. as annotations of the claim, they are defined here, in a separate resource. """ _log = _logger.new(resource=resource, rule=resource.obj) spec = resource.obj.get('spec', {}) # Validate the deltas try: deltas_str = resource.obj.get('spec', {}).get('deltas') try: deltas = parse_deltas(deltas_str) except DeltasParseError as exc: raise AnnotationError( 'Invalid delta string', deltas_str=deltas_str ) from exc if deltas is None or not deltas: raise AnnotationError( 'parse_deltas returned invalid deltas', deltas_str=deltas_str, deltas=deltas, ) except AnnotationError: _log.exception( 'rule.invalid', key_hints=['rule.metadata.name'], ) return # Refers to a disk from a cloud provider if spec.get('disk'): # Validate the backend backend_name = spec.get('backend') try: backend = get_backend(backend_name) except ConfigurationError as e: _log.exception( 'rule.invalid', message=e.message, backend=backend_name ) return # Validate the disk identifier disk = resource.obj.get('spec', {}).get('disk') try: disk = backend.validate_disk_identifier(disk) except ValueError: _log.exception( 'rule.invalid', key_hints=['rule.metadata.name'], ) return rule = Rule( name=rule_name_from_k8s_source(resource), deltas=deltas, backend=backend_name, disk=disk ) return rule # Refers to a volume claim if spec.get('persistentVolumeClaim'): # Find the claim volume_claim = await get_resource_or_none( ctx.kube_client, pykube.objects.PersistentVolumeClaim, spec.get('persistentVolumeClaim'), namespace=resource.metadata['namespace'] ) if not volume_claim: _log.warning( events.Rule.PENDING, reason='Volume claim does not exist', key_hints=['rule.metadata.name'], ) raise RuleDependsOn( 'The volume claim targeted by this SnapshotRule does not exist yet', kind='PersistentVolumeClaim', namespace=resource.metadata['namespace'], name=spec.get('persistentVolumeClaim') ) # Find the volume try: volume = await volume_from_pvc(ctx, volume_claim) except VolumeNotFound: _log.warning( events.Rule.PENDING, reason='Volume claim is not bound', key_hints=['rule.metadata.name'], ) raise RuleDependsOn( 'The volume claim targeted by this SnapshotRule is not bound yet', kind='PersistentVolumeClaim', namespace=resource.metadata['namespace'], name=spec.get('persistentVolumeClaim') ) return await rule_from_pv(ctx, volume, deltas, source=resource)
async def rule_from_resource( ctx: Context, resource: Union[pykube.objects.PersistentVolume, pykube.objects.PersistentVolumeClaim, SnapshotRule] ) -> Rule: """Given a Kubernetes resource, converts it to a snapshot `Rule` instance, or returns None. How this process works, depends on the resource given. - If the resource is a volume, we read the disk and delta info from there. - If the resource is a volume claim, we look for the volume. - A `SnapshotRule` custom resource directly defines the disk. """ _log = _logger.new(resource=resource, ) if (isinstance(resource, SnapshotRule)): # Validate the backen backend_name = resource.obj.get('spec', {}).get('backend') try: backend = get_backend(backend_name) except ConfigurationError as e: _log.exception('rule.invalid', message=e.message, backend=backend_name) return # Validate the deltas try: deltas_str = resource.obj.get('spec', {}).get('deltas') try: deltas = parse_deltas(deltas_str) except DeltasParseError as exc: raise AnnotationError('Invalid delta string', deltas_str=deltas_str) from exc if deltas is None or not deltas: raise AnnotationError( 'parse_deltas returned invalid deltas', deltas_str=deltas_str, deltas=deltas, ) except AnnotationError: _log.exception( 'rule.invalid', key_hints=['volume.metadata.name'], ) return # Validate the disk identifier disk = resource.obj.get('spec', {}).get('disk') try: disk = backend.validate_disk_identifier(disk) except ValueError: _log.exception( 'rule.invalid', key_hints=['volume.metadata.name'], ) return rule = Rule(name=rule_name_from_k8s_source(resource), deltas=deltas, backend=backend_name, disk=disk) return rule if isinstance(resource, pykube.objects.PersistentVolumeClaim): try: volume = await volume_from_pvc(ctx, resource) except VolumeNotFound: _log.exception( events.Volume.NOT_FOUND, key_hints=[ 'resource.metadata.name', ], ) return elif isinstance(resource, pykube.objects.PersistentVolume): volume = resource else: raise RuntimeError(f'{resource} is not supported.') volume_name = volume.name _log = _log.bind( volume_name=volume_name, volume=volume.obj, ) try: rule = await rule_from_pv( ctx, volume, ctx.config.get('deltas_annotation_key'), use_claim_name=ctx.config.get('use_claim_name')) return rule except AnnotationNotFound as exc: _log.info( events.Annotation.NOT_FOUND, key_hints=['volume.metadata.name'], exc_info=exc, ) except AnnotationError: _log.exception( events.Annotation.ERROR, key_hints=['volume.metadata.name'], ) except UnsupportedVolume as exc: _log.info( events.Volume.UNSUPPORTED, key_hints=['volume.metadata.name'], exc_info=exc, )