Coverage for ion/services/coi/resource_registry/resource_client : 83.92%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
|
#!/usr/bin/env python
@file ion/services/coi/resource_registry/resource_client.py @author David Stuebe @brief Resource Client and and Resource Instance classes are used to manage resource objects in services and processes. They provide a simple interface to create, get, put and update resources.
@ TODO Add methods to access the state of updates which are merging... """
""" A class for resource client exceptions """
""" @brief This is the base class for a resource client. It is a factory for resource instances. The resource instance provides the interface for working with resources. The client helps create and manage resource instances. """
# The type_map is a map from object type to resource type built from the ion_preload_configs # this is a temporary device until the resource registry is fully architecturally operational.
""" Initializes a process client @param proc a IProcess instance as originator of messages @param datastore the name of the datastore service with which you wish to interact with the OOICI. """
# The resource client is backed by a process workbench.
#self.asc = AssociationServiceClient(proc=proc)
# What about the name of the index services to use?
# Make a weak value dictionary to hold the resource instance - make sure there is only one wrapper for each repository
def _check_init(self): """ Called in client methods to ensure that there exists a spawned process to send and receive messages """
'Process workbench is not initialized'
""" @brief Ask the resource registry to create the instance! @param type_id is a type identifier object @param name is a string, a name for the new resource @param description is a string describing the resource @retval resource is a ResourceInstance object
@TODO Change the create method to take a Resource Type ID - change RR to look up object type! @TODO pull the associations that go with this resource
"""
# Create a sendable resource object
# Set the description
# This is breaking some abstractions - using the GPB directly...
# Set the resource type - keep the object type above for now...
# Get the resource type if it exists - otherwise a default will be set!
# Use the registry client to make a new resource
except workbench.WorkBenchError, ex: raise ResourceClientError('Pull from datastore failed in resource client! Resource Not Found!\nInner Exception:\n%s' % str(ex))
""" @brief Get the latest version of the identified resource from the data store @param resource_id can be either a string resource identity or an IDRef object which specifies the resource identity as well as optional parameters version and version state. @retval the specified ResourceInstance
"""
# Get the type of the argument and act accordingly # If it is a resource reference, unpack it.
commit = resource_id.commit
treeish = resource_id.treeish
# if it is a string, us it as an identity
# @TODO Some reasonable test to make sure it is valid?
else: raise ResourceClientError('''Illegal argument type in get_instance: \n type: %s \nvalue: %s''' % (type(resource_id), str(resource_id)))
# Pull the repository 'Could not pull the requested resource from the datastore. Workbench exception: \n %s' % ex)
# Get the repository
# do we have a treeish to resolve? commit = commitref.MyId
# Create a resource instance to return # @TODO - Check and see if there is already one - what to do?
else: # Use the existing one - it is basically stateless...
# Is this a good use of the resource name? Is it safe?
# Get owner and ownership association: #owner_associations = yield self.get_associations(subject=resource, predicate_or_predicates=OWNED_BY_ID)
""" @Brief Write the current state of the resource and any associations to the data store @param instance is a ResourceInstance object to be written @param comment is a comment to add about the current state of the resource
@TODO push the associations that go with this resource """
# Get the repository
except workbench.WorkBenchError, ex: raise ResourceClientError('Push to datastore failed during put_instance, inner exception:\n%s' % str(ex))
""" @Brief Write the current state of a list of resources to the data store @param instance is a ResourceInstance object. All associations and all associated objects will be pushed. @param comment is a comment to add about the current state of the resource
@TODO push the associations that go with this resource """
raise ResourceClientError('Must pass at least one resource instance to put_resource_transaction')
# Check to make sure they are valid resource instances raise ResourceClientError( 'Invalid object in list of instances argument to put_resource_transaction. Must be an Instance, received: "%s"' % str( instance)) else: raise ResourceClientError( 'Invalid argument to put_resource_transaction: instances must be a resource instance or a list of them')
except workbench.WorkBenchError, ex: raise ResourceClientError('Push to datastore failed during put_resource_transaction, inner exception:\n %s' % str(ex))
def get_associated_resource_object(self, association): """ @Brief Get the Resource Instance which is the object of the association @param association is an association instance """ raise ResourceClientError( 'Invalid argument to get_associated_resource_object: argument must be an association instance')
# Get the latest of that resource
def get_associated_resource_subject(self, association): """ @Brief Get the Resource Instance which is the object of the association @param association is an association instance """ raise ResourceClientError( 'Invalid argument to get_associated_resource_subject: argument must be an association instance')
# Get the latest of that resource
""" @brief Reference Resource creates a data object which can be used as a message or part of a message or added to another data object or resource. @param instance is a ResourceInstance object @param current_state is a boolen argument which determines whether you intend to reference exactly the current state of the resource. @retval an Identity Reference object to the resource """
# @TODO yield self._check_init()
""" Exception class for Resource Instance Object """
raise AttributeError('Can not delete a Resource Instance property')
raise AttributeError('Can not delete a Resource Instance property')
raise AttributeError('Can not set a Resource Instance enum object')
raise AttributeError('Can not delete a Resource Instance property')
""" Metaclass that automatically generates subclasses of Wrapper with corresponding enums and pass-through properties for each field in the protobuf descriptor.
This approach is generally applicable to wrap data structures. It is extremely powerful! """
# Cache the custom-built classes
# Check that the object we are wrapping is a Google Message object raise ResourceInstanceError('MergeResourceInstance init argument must be an instance of a MergeRepository')
raise ResourceInstanceError('MergeResourceInstance init Repository is not a MergeRepository!')
else: # Get the class name
#print 'Key: %s; Type: %s' % (fieldName, type(message_field))
# Try rewriting using slots - would be more efficient... # For the resource instance, there is only one class propety to set, # so just create it using object.__setattr__ raise AttributeError(\ '''Cant add properties to the ION Resource Instance.\n''' '''Unknown property name - "%s"; value - "%s"''' % (k, v))
# Finally allow the instantiation to occur, but slip in our new class type
""" @brief The resource instance is the vehicle through which a process interacts with a resource instance. It hides the git semantics of the data store and deals with resource specific properties.
@TODO how do we make sure that there is only one resource instance per resource? If the process creates multiple resource instances to wrap the same resource we are in trouble. Need to find a way to keep a cache of the instances """
""" Merge Resource Instance objects are created by the resource client """
def MergeRepository(self): return self._merge_repository
def Resource(self): repo = self._merge_repository return repo.root_object
def ResourceObject(self):
""" Return a list of the names of the fields which have been set. """ return self.ResourceObject.ListSetFields()
return self.ResourceObject.IsFieldSet(field)
def ResourceIdentity(self): """ @brief Return the resource identity as a string """ return str(self.Resource.identity)
def ResourceObjectType(self): """ @brief Returns the resource type - A type identifier object - not the wrapped object. """ # Resource type should be a Resource Identifier - UUID defined in preload_config and stored in the Resource Registry
return self.Resource.object_type.GPBMessage
def ResourceTypeID(self): """ @brief Returns the resource type identifier - the idref for the resource type instance. """ # Resource type should be a Resource Identifier - UUID defined in preload_config and stored in the Resource Registry
return self.Resource.resource_type
def ResourceLifeCycleState(self): """ @brief Get the life cycle state of the resource """ state = None if self.Resource.lcs == self.Resource.LifeCycleState.NEW: state = self.NEW
elif self.Resource.lcs == self.Resource.LifeCycleState.ACTIVE: state = self.ACTIVE
elif self.Resource.lcs == self.Resource.LifeCycleState.INACTIVE: state = self.INACTIVE
elif self.Resource.lcs == self.Resource.LifeCycleState.COMMISSIONED: state = self.COMMISSIONED
elif self.Resource.lcs == self.Resource.LifeCycleState.DECOMMISSIONED: state = self.DECOMMISSIONED
elif self.Resource.lcs == self.Resource.LifeCycleState.RETIRED: state = self.RETIRED
elif self.Resource.lcs == self.Resource.LifeCycleState.DEVELOPED: state = self.DEVELOPED
elif self.Resource.lcs == self.Resource.LifeCycleState.UPDATE: state = self.UPDATE
return state
def ResourceName(self): return self.Resource.name
def ResourceDescription(self): return self.Resource.description
return self.merge_repos.__iter__()
return len(self.merge_repos)
""" Metaclass that automatically generates subclasses of Wrapper with corresponding enums and pass-through properties for each field in the protobuf descriptor.
This approach is generally applicable to wrap data structures. It is extremely powerful! """
# Cache the custom-built classes
# Check that the object we are wrapping is a Google Message object raise ResourceInstanceError('ResourceInstance init argument must be an instance of a Repository')
raise ResourceInstanceError( 'ResourceInstance init Repository argument is in an invalid state - checkout first!')
raise ResourceInstanceError('ResourceInstance init Repository is not a resource object!')
else: # Get the class name
#print 'Key: %s; Type: %s' % (fieldName, type(message_field))
# Try rewriting using slots - would be more efficient... # For the resource instance, there is only one class propety to set, # so just create it using object.__setattr__ raise AttributeError(\ '''Cant add properties to the ION Resource Instance.\n''' '''Unknown property name - "%s"; value - "%s"''' % (k, v))
# Finally allow the instantiation to occur, but slip in our new class type
""" @brief The resource instance is the vehicle through which a process interacts with a resource instance. It hides the git semantics of the data store and deals with resource specific properties.
@TODO how do we make sure that there is only one resource instance per resource? If the process creates multiple resource instances to wrap the same resource we are in trouble. Need to find a way to keep a cache of the instances """
# Life Cycle States
# Resource update mode
# Resource update Resolutions
""" Resource Instance objects are created by the resource client """
def Repository(self):
def ResourceAssociationsAsSubject(self):
def ResourceAssociationsAsObject(self):
def Resource(self):
raise ResourceInstanceError('Can not change the type of a resource object!')
def Merge(self): """ @ Brief This method provides access to the committed resource states that are bing merged into the current version of the Resource. """
# Do some synchronization with the repository:
# Clear the Resource clients list of merge stuff
""" @brief Create a new version of this resource - creates a new branch in the objects repository. This is purely local until the next push! @retval the key for the new version """
# Set the LCS in the resource branch to UPDATE and the object to the update
log.error('Resource Type does not match update Type!') log.error('Update type %s; Resource type %s' % (str(update.ObjectType), str(self.ResourceObjectType))) raise ResourceInstanceError( 'CreateUpdateBranch argument "update" must be of the same type as the resource to be updated!')
# Copy the update object into resource as the current state object.
return self.Repository.current_branch_key()
def MergeResourceUpdate(self, mode, *args): """ Use this method when updating an existing resource. This is the recommended pattern for updating a resource. The Resource history will include a special Branch pattern showing the previous state, the update and the updated state... Once an update is commited, the update must be resolved before the instance can be put (pushed) to the public datastore.
<Updated State> | \ \ | <Update1> <Update2> ... | / / <Previous State>
""" raise ResourceInstanceError('Can not merge while the resource is in a modified state')
log.debug('Resource Type does not match update Type') log.debug('Update type %s; Resource type %s' % (str(update.ObjectType), str(self.ResourceObjectType))) raise ResourceInstanceError( 'update_instance argument "update" must be of the same type as the resource')
# Create and switch to a new branch
# Set the LCS in the resource branch to UPDATE and the object to the update
# Copy the update object into resource as the current state object.
# Set up the merge in the repository
# Remove the merge branch - it is only a local concern # on the next commit - when put_instance is called - the merge will be complete!
""" @brief CreateObject is used to make new locally create objects which can be added to the resource's data structure. @param type_id is the type_id of the object to be created @retval the new object which can now be attached to the resource """
""" Return a list of the names of the fields which have been set. """
log.warn('HasField is depricated because the name is confusing. Use IsFieldSet') return self.IsFieldSet(field)
return self.ResourceObject.ClearField(field)
def ResourceIdentity(self): """ @brief Return the resource identity as a string """
def ResourceObjectType(self): """ @brief Returns the resource type - A type identifier object - not the wrapped object. """ # Resource type should be a Resource Identifier - UUID defined in preload_config and stored in the Resource Registry
def ResourceTypeID(self): """ @brief Returns the resource type identifier - the idref for the resource type instance. """ # Resource type should be a Resource Identifier - UUID defined in preload_config and stored in the Resource Registry
""" @brief Set the Life Cycel State of the resource @param state is a resource life cycle state class variable defined in the ResourceInstance class. """ # Using IS for comparison - I think this is better than the usual == # Want to force the use of the self.XXXX as the argument!
self.Resource.lcs = self.Resource.LifeCycleState.INACTIVE self.Resource.lcs = self.Resource.LifeCycleState.COMMISSIONED self.Resource.lcs = self.Resource.LifeCycleState.DECOMMISSIONED self.Resource.lcs = self.Resource.LifeCycleState.DEVELOPED else: raise Exception('''Invalid argument value state: %s. State must be one of the class variables defined in Resource Instance''' % str(state))
""" @brief Get the life cycle state of the resource """
state = self.INACTIVE
state = self.COMMISSIONED
state = self.DECOMMISSIONED
state = self.DEVELOPED
""" @var ResourceLifeCycleState is a getter setter property for the life cycle state of the resource """
""" Set the name of the resource object """
""" """
""" @var ResourceName is a getter setter property for the name of the resource """
""" """ self.Resource.description = description
""" """
@var ResourceDescription is a getter setter property for the description of the resource """ |