diff --git a/platform/src/main/python/dlpx/virtualization/platform/_linked.py b/platform/src/main/python/dlpx/virtualization/platform/_linked.py index dbc2a94c..f9aab410 100644 --- a/platform/src/main/python/dlpx/virtualization/platform/_linked.py +++ b/platform/src/main/python/dlpx/virtualization/platform/_linked.py @@ -6,6 +6,7 @@ """LinkedOperations for the Virtualization Platform """ +import inspect import json from dlpx.virtualization.api import common_pb2, platform_pb2 @@ -13,7 +14,7 @@ from dlpx.virtualization.common.exceptions import PluginRuntimeError from dlpx.virtualization.platform import (DirectSource, Mount, MountSpecification, StagedSource, - Status, PhysicalSource) + Status, PhysicalSource, PitParameters) from dlpx.virtualization.platform import validation_util as v from dlpx.virtualization.platform.exceptions import ( IncorrectReturnTypeError, OperationAlreadyDefinedError, @@ -265,12 +266,17 @@ def to_protobuf(snapshot): None if snap_params is None else SnapshotParametersDefinition.from_dict(snap_params)) - snapshot = self.post_snapshot_impl( + result = self.post_snapshot_impl( direct_source=direct_source, repository=repository, source_config=source_config, optional_snapshot_parameters=snapshot_parameters) + if isinstance(result, tuple): + snapshot, pit_parameters = result + else: + snapshot, pit_parameters = result, None + # Validate that this is a SnapshotDefinition object if not isinstance(snapshot, SnapshotDefinition): raise IncorrectReturnTypeError(Op.LINKED_POST_SNAPSHOT, @@ -280,6 +286,9 @@ def to_protobuf(snapshot): platform_pb2.DirectPostSnapshotResponse()) direct_post_snapshot_response.return_value.snapshot.CopyFrom( to_protobuf(snapshot)) + if pit_parameters is not None: + direct_post_snapshot_response.return_value.pit_parameters.CopyFrom( + pit_parameters.to_proto()) return direct_post_snapshot_response @@ -529,12 +538,17 @@ def to_protobuf(snapshot): None if snap_params is None else SnapshotParametersDefinition.from_dict(snap_params)) - snapshot = self.post_snapshot_impl( + result = self.post_snapshot_impl( staged_source=staged_source, repository=repository, source_config=source_config, optional_snapshot_parameters=snapshot_parameters) + if isinstance(result, tuple): + snapshot, pit_parameters = result + else: + snapshot, pit_parameters = result, None + # Validate that this is a SnapshotDefinition object if not isinstance(snapshot, SnapshotDefinition): raise IncorrectReturnTypeError(Op.LINKED_POST_SNAPSHOT, @@ -542,6 +556,9 @@ def to_protobuf(snapshot): response = platform_pb2.StagedPostSnapshotResponse() response.return_value.snapshot.CopyFrom(to_protobuf(snapshot)) + if pit_parameters is not None: + response.return_value.pit_parameters.CopyFrom( + pit_parameters.to_proto()) return response diff --git a/platform/src/main/python/dlpx/virtualization/platform/_plugin_classes.py b/platform/src/main/python/dlpx/virtualization/platform/_plugin_classes.py index e26f8046..65559ccd 100644 --- a/platform/src/main/python/dlpx/virtualization/platform/_plugin_classes.py +++ b/platform/src/main/python/dlpx/virtualization/platform/_plugin_classes.py @@ -22,7 +22,7 @@ __all__ = [ "VirtualSource", "StagedSource", "DirectSource", "PhysicalSource", "Status", "MigrationType", "Mount", "OwnershipSpecification", - "MountSpecification" + "MountSpecification", "LogFile", "PitParameters" ] @@ -287,6 +287,76 @@ def gid(self): return self._gid +class LogFile(object): + def __init__(self, filename, location, timestamp, parameters=None): + self._filename = filename + self._location = location + self._timestamp = timestamp + self._parameters = parameters or {} + + @property + def filename(self): + return self._filename + + @property + def location(self): + return self._location + + @property + def timestamp(self): + return self._timestamp + + @property + def parameters(self): + return self._parameters + + def to_proto(self): + from dlpx.virtualization.api import common_pb2 + proto = common_pb2.LogFile() + proto.filename = self._filename + proto.location = self._location + proto.timestamp.FromDatetime(self._timestamp) + proto.parameters.update(self._parameters) + return proto + + @classmethod + def from_proto(cls, proto): + return cls( + filename=proto.filename, + location=proto.location, + timestamp=proto.timestamp.ToDatetime(), + parameters=dict(proto.parameters), + ) + + +class PitParameters(object): + def __init__(self, pit_timestamp, log_files=None): + self._pit_timestamp = pit_timestamp + self._log_files = log_files or [] + + @property + def pit_timestamp(self): + return self._pit_timestamp + + @property + def log_files(self): + return self._log_files + + def to_proto(self): + from dlpx.virtualization.api import common_pb2 + proto = common_pb2.PitParameters() + proto.pit_timestamp.FromDatetime(self._pit_timestamp) + proto.log_files.extend([lf.to_proto() for lf in self._log_files]) + return proto + + @classmethod + def from_proto(cls, proto): + return cls( + pit_timestamp=proto.pit_timestamp.ToDatetime(), + log_files=[LogFile.from_proto(lf) for lf in proto.log_files], + ) + + class MountSpecification(object): def __init__(self, mounts, ownership_specification=None): if not isinstance(mounts, list): diff --git a/platform/src/main/python/dlpx/virtualization/platform/_virtual.py b/platform/src/main/python/dlpx/virtualization/platform/_virtual.py index f947efad..1c1c8471 100644 --- a/platform/src/main/python/dlpx/virtualization/platform/_virtual.py +++ b/platform/src/main/python/dlpx/virtualization/platform/_virtual.py @@ -6,12 +6,14 @@ """VirtualOperations for the Virtualization Platform """ +import inspect import json from dlpx.virtualization.api import common_pb2, platform_pb2 from dlpx.virtualization.common import RemoteConnection, RemoteEnvironment from dlpx.virtualization.platform import (Mount, MountSpecification, Status, - VirtualSource, PhysicalSource) + VirtualSource, PhysicalSource, + PitParameters) from dlpx.virtualization.platform import validation_util as v from dlpx.virtualization.platform.exceptions import ( IncorrectReturnTypeError, OperationAlreadyDefinedError, @@ -221,9 +223,16 @@ def _internal_configure(self, request): snapshot = SnapshotDefinition.from_dict( json.loads(request.snapshot.parameters.json)) - config = self.configure_impl(virtual_source=virtual_source, - repository=repository, - snapshot=snapshot) + pit_parameters = ( + PitParameters.from_proto(request.pit_parameters) + if request.HasField('pit_parameters') else None) + + kwargs = dict(virtual_source=virtual_source, + repository=repository, + snapshot=snapshot) + if 'pit_parameters' in inspect.signature(self.configure_impl).parameters: + kwargs['pit_parameters'] = pit_parameters + config = self.configure_impl(**kwargs) # Validate that this is a SourceConfigDefinition object. if not isinstance(config, SourceConfigDefinition): @@ -382,10 +391,17 @@ def _internal_reconfigure(self, request): repository = RepositoryDefinition.from_dict( json.loads(request.repository.parameters.json)) - config = self.reconfigure_impl(snapshot=snapshot, - repository=repository, - source_config=source_config, - virtual_source=virtual_source) + pit_parameters = ( + PitParameters.from_proto(request.pit_parameters) + if request.HasField('pit_parameters') else None) + + kwargs = dict(snapshot=snapshot, + repository=repository, + source_config=source_config, + virtual_source=virtual_source) + if 'pit_parameters' in inspect.signature(self.reconfigure_impl).parameters: + kwargs['pit_parameters'] = pit_parameters + config = self.reconfigure_impl(**kwargs) # Validate that this is a SourceConfigDefinition object. if not isinstance(config, SourceConfigDefinition): diff --git a/platform/src/main/python/dlpx/virtualization/platform/import_validations.py b/platform/src/main/python/dlpx/virtualization/platform/import_validations.py index 6d512f94..cd05cc22 100644 --- a/platform/src/main/python/dlpx/virtualization/platform/import_validations.py +++ b/platform/src/main/python/dlpx/virtualization/platform/import_validations.py @@ -174,15 +174,23 @@ def _check_upgrade_args(upgrade_operations, expected_upgrade_args): return warnings +# Args that plugins may optionally declare to opt into SDK-provided features. +# The SDK passes these only when the plugin's function signature includes them. +_OPTIONAL_ARGS = frozenset({'pit_parameters'}) + + def _check_args(method_name, expected_args, actual_args): warnings = [] - if len(expected_args) != len(actual_args): + required_actual = [a for a in actual_args if a not in _OPTIONAL_ARGS] + + if len(expected_args) != len(required_actual): warnings.append('Number of arguments do not match in method {}.' ' Expected: {}, Found: {}.'.format( method_name, list(expected_args), actual_args)) - if not all(arg in expected_args for arg in actual_args): + if not all(arg in expected_args or arg in _OPTIONAL_ARGS + for arg in actual_args): warnings.append('Named argument mismatch in method {}.' ' Expected: {}, Found: {}.'.format( method_name, list(expected_args), actual_args))