class AgentConfigurationBuilder(object):

    def __init__(self, clients, RR2=None):
        self.clients = clients
        self.RR2 = RR2

        if self.RR2 is None:
            log.warn("Creating new RR2")
            self.RR2 = EnhancedResourceRegistryClient(self.clients.resource_registry)

        if not isinstance(self.RR2, EnhancedResourceRegistryClient):
            raise AssertionError("Type of self.RR2 is %s not %s" %
                                 (type(self.RR2), type(EnhancedResourceRegistryClient)))

        self.agent_instance_obj = None
        self.associated_objects = None
        self.last_id            = None
        self.will_launch        = False
        self.generated_config   = False

    def _predicates_to_cache(self):
        return [PRED.hasOutputProduct,
                #PRED.hasStream,
                #PRED.hasStreamDefinition,
                PRED.hasAgentInstance,
                PRED.hasAgentDefinition,
                PRED.hasDataset,
                PRED.hasDevice,
                PRED.hasNetworkParent,
                #PRED.hasParameterContext,
                PRED.hasDeployment,
                ]

    def _resources_to_cache(self):
        return [#RT.StreamDefinition,
                RT.ParameterDictionary,
                #RT.ParameterContext,
                RT.Deployment,
                ]

    def _update_cached_predicates(self):
        # cache some predicates for in-memory lookups
        preds = self._predicates_to_cache()
        log.debug("updating cached predicates: %s" % preds)
        time_caching_start = get_ion_ts()
        for pred in preds:
            log.debug(" - %s", pred)
            self.RR2.cache_predicate(pred)
        time_caching_stop = get_ion_ts()

        total_time = int(time_caching_stop) - int(time_caching_start)

        log.info("Cached %s predicates in %s seconds", len(preds), total_time / 1000.0)

    def _update_cached_resources(self):
        # cache some resources for in-memory lookups
        rsrcs = self._resources_to_cache()
        log.debug("updating cached resources: %s" % rsrcs)
        time_caching_start = get_ion_ts()
        for r in rsrcs:
            log.debug(" - %s", r)
            self.RR2.cache_resources(r)
        time_caching_stop = get_ion_ts()

        total_time = int(time_caching_stop) - int(time_caching_start)

        log.info("Cached %s resource types in %s seconds", len(rsrcs), total_time / 1000.0)

    def _clear_caches(self):
        log.warn("Clearing caches")
        for r in self._resources_to_cache():
            self.RR2.clear_cached_resource(r)

        for p in self._predicates_to_cache():
            self.RR2.clear_cached_predicate(p)
            

    def _lookup_means(self):
        """
        return a dict indicating how various related resources will be looked up

        The dict is keyed on association type:
        PRED.hasAgentInstance -> device type
        PRED.hasModel -> model type
        PRED.hasAgentDefinition -> agent type
        """
        raise NotImplementedError("Extender of class must implement this")

    def _augment_dict(self, title, basedict, newitems):
        # TODO: pyon.util.containers has dict_merge for this purpose (without logs)
        for k, v in newitems.iteritems():
            if k in basedict:
                prev_v = basedict[k]
                # just warn if the new value is different
                if v != prev_v:
                    log.warn("Overwriting %s[%s] of '%s' with '%s'", title, k, prev_v, v)
                else:
                    log.debug("Overwriting %s[%s] with same value already assigned '%s'",
                              title, k, v)
            basedict[k] = v

    def _check_associations(self):
        assert self.agent_instance_obj
        assert self.associated_objects

        lookup_means = self._lookup_means()
        assert lookup_means

        # make sure we've picked up the associations we expect
        def check_keys(somekeys):
            for k in somekeys:
                assert k in lookup_means
                assert lookup_means[k] in self.associated_objects

        #check_keys([PRED.hasAgentInstance, PRED.hasModel, PRED.hasAgentDefinition])
        check_keys([PRED.hasAgentInstance, PRED.hasAgentDefinition])
        assert RT.ProcessDefinition in self.associated_objects


    def set_agent_instance_object(self, agent_instance_obj):
        """
        Set the agent instance object that we'll be interacting with

        it may be necessary to set this several times, such as if external operations update the object
        """
        assert agent_instance_obj._id

        if self.last_id != agent_instance_obj._id:
            self.associated_objects = None

        self.agent_instance_obj = agent_instance_obj
        self.last_id = agent_instance_obj._id
        self.generated_config = False

    def prepare(self, will_launch=True):
        """
        Prepare (validate) an agent for launch, fetching all associated resources

        @param will_launch - whether the running status should be checked -- set false if just generating config
        """
        assert self.agent_instance_obj

        # fetch caches just in time
        if any([not self.RR2.has_cached_predicate(x) for x in self._predicates_to_cache()]):
            self._update_cached_predicates()

        if any([not self.RR2.has_cached_resource(x) for x in self._resources_to_cache()]):
            self._update_cached_resources()

        # validate the associations, then pick things up
        self._collect_agent_instance_associations()

        if will_launch:
            # if there is an agent pid then assume that a drive is already started
            agent_process_id = ResourceAgentClient._get_agent_process_id(self._get_device()._id)
            if agent_process_id:
                raise BadRequest("Agent Instance already running for this device pid: %s" %
                                 str(agent_process_id))

        self.will_launch = will_launch
        config = self.generate_config()
        return config


    def _generate_org_governance_name(self):
        log.debug("_generate_org_governance_name for %s", self.agent_instance_obj.name)
        log.debug("retrieve the Org governance name to which this agent instance belongs")
        try:
            org_obj = self.RR2.find_subject(RT.Org, PRED.hasResource, self.agent_instance_obj._id, id_only=False)
            return org_obj.org_governance_name
        except NotFound:
            return ''
        except:
            raise

    def _generate_device_type(self):
        log.debug("_generate_device_type for %s", self.agent_instance_obj.name)
        return type(self._get_device()).__name__

    def _generate_driver_config(self):
        log.debug("_generate_driver_config for %s", self.agent_instance_obj.name)
        # get default config
        driver_config = self.agent_instance_obj.driver_config

        agent_obj = self._get_agent()

        # Create driver config.
        add_driver_config = {
            'workdir'      : tempfile.gettempdir(),
            'dvr_mod'      : agent_obj.driver_module,
            'dvr_cls'      : agent_obj.driver_class
        }

        self._augment_dict("Agent driver_config", driver_config, add_driver_config)

        return driver_config

    def _get_param_dict_by_name(self, name):
        dict_obj = self.RR2.find_resources_by_name(RT.ParameterDictionary, name)[0]
        parameter_contexts = \
            self.RR2.find_parameter_contexts_of_parameter_dictionary_using_has_parameter_context(dict_obj._id)
        return DatasetManagementService.build_parameter_dictionary(dict_obj, parameter_contexts)

    def _find_streamdef_for_dp_and_pdict(self, dp_id, pdict_id):
        # Given a pdict_id and a data_product_id find the stream def in the middle
        pdict_stream_defs = self.RR2.find_stream_definition_ids_by_parameter_dictionary_using_has_parameter_dictionary(pdict_id)
        stream_def_id = self.RR2.find_stream_definition_id_of_data_product_using_has_stream_definition(dp_id)
        result = stream_def_id if stream_def_id in pdict_stream_defs else None

        return result


    def _generate_stream_config(self):
        log.debug("_generate_stream_config for %s", self.agent_instance_obj.name)
        dsm = self.clients.dataset_management
        psm = self.clients.pubsub_management

        agent_obj  = self._get_agent()
        device_obj = self._get_device()

        streams_dict = {}
        for stream_cfg in agent_obj.stream_configurations:
            #create a stream def for each param dict to match against the existing data products
            streams_dict[stream_cfg.stream_name] = {'param_dict_name':stream_cfg.parameter_dictionary_name}

        #retrieve the output products
        # TODO: What about platforms? other things?
        device_id = device_obj._id
        data_product_objs = self.RR2.find_data_products_of_instrument_device_using_has_output_product(device_id)

        stream_config = {}
        for dp in data_product_objs:
            stream_def_id = self.RR2.find_stream_definition_id_of_data_product_using_has_stream_definition(dp._id)
            for stream_name, stream_info_dict in streams_dict.items():
                # read objects from cache to be compared
                pdict = self.RR2.find_resource_by_name(RT.ParameterDictionary, stream_info_dict.get('param_dict_name'))
                stream_def_id = self._find_streamdef_for_dp_and_pdict(dp._id, pdict._id)

                if stream_def_id:
                    #model_param_dict = self.RR2.find_resources_by_name(RT.ParameterDictionary,
                    #                                         stream_info_dict.get('param_dict_name'))[0]
                    #model_param_dict = self._get_param_dict_by_name(stream_info_dict.get('param_dict_name'))
                    #stream_route = self.RR2.read(product_stream_id).stream_route
                    product_stream_id = self.RR2.find_stream_id_of_data_product_using_has_stream(dp._id)
                    stream_def = psm.read_stream_definition(stream_def_id)
                    stream_route = psm.read_stream_route(stream_id=product_stream_id)

                    from pyon.core.object import IonObjectSerializer
                    stream_def_dict = IonObjectSerializer().serialize(stream_def)
                    stream_def_dict.pop('type_')

                    if stream_name in stream_config:
                        log.warn("Overwriting stream_config[%s]", stream_name)

                    stream_config[stream_name] = {  'routing_key'           : stream_route.routing_key,  # TODO: Serialize stream_route together
                                                    'stream_id'             : product_stream_id,
                                                    'stream_definition_ref' : stream_def_id,
                                                    'stream_def_dict'       : stream_def_dict,  # This is very large
                                                    'exchange_point'        : stream_route.exchange_point,
                                                    # This is redundant and very large - the param dict is in the stream_def_dict
                                                    #'parameter_dictionary'  : stream_def.parameter_dictionary,

                    }
        if len(stream_config) < len(streams_dict):
            log.warn("Found only %s matching streams by stream definition (%s) than %s defined in the agent (%s).",
                     len(stream_config), stream_config.keys(), len(streams_dict), streams_dict.keys())

        log.debug("Stream config generated")
        log.trace("generate_stream_config: %s", stream_config)
        return stream_config

    def _generate_agent_config(self):
        log.debug("_generate_agent_config for %s", self.agent_instance_obj.name)
        agent_config = {}

        # Set the agent state vector from the prior agent run
        if self.agent_instance_obj.saved_agent_state:
            agent_config["prior_state"] = self.agent_instance_obj.saved_agent_state

        return agent_config

    def _generate_alerts_config(self):
        log.debug("_generate_alerts_config for %s", self.agent_instance_obj.name)
        # should override this
        return self.agent_instance_obj.alerts

    def _generate_startup_config(self):
        log.debug("_generate_startup_config for %s", self.agent_instance_obj.name)
        # should override this
        return {}

    def _generate_children(self):
        log.debug("_generate_children for %s", self.agent_instance_obj.name)
        # should override this
        return {}

    def _generate_skeleton_config_block(self):
        log.info("Generating skeleton config block for %s", self.agent_instance_obj.name)

        # merge the agent config into the default config
        agent_config = dict_merge(self._get_agent().agent_default_config, self.agent_instance_obj.agent_config, True)

        # Create agent_config.
        agent_config['instance_id']        = self.agent_instance_obj._id
        agent_config['instance_name']        = self.agent_instance_obj.name
        agent_config['org_governance_name']  = self._generate_org_governance_name()
        agent_config['device_type']          = self._generate_device_type()
        agent_config['driver_config']        = self._generate_driver_config()
        agent_config['stream_config']        = self._generate_stream_config()
        agent_config['agent']                = self._generate_agent_config()
        agent_config['aparam_alerts_config'] = self._generate_alerts_config()
        agent_config['startup_config']       = self._generate_startup_config()
        agent_config['children']             = self._generate_children()

        log.info("DONE generating skeleton config block for %s", self.agent_instance_obj.name)

        return agent_config


    def _summarize_children(self, config_dict):
        ret = dict([(v['instance_name'], self._summarize_children(v))
                                for k, v in config_dict["children"].iteritems()])
        #agent_config['agent']['resource_id']
        return ret

    def generate_config(self):
        """
        create the generic parts of the configuration including resource_id, egg_uri, and org
        """
        if self.generated_config:
            log.warn("Generating config again for the same Instance object (%s)", self.agent_instance_obj.name)

        self._check_associations()

        agent_config = self._generate_skeleton_config_block()

        device_obj = self._get_device()
        agent_obj  = self._get_agent()

        log.debug("complement agent_config with resource_id")
        if 'agent' not in agent_config:
            agent_config['agent'] = {'resource_id': device_obj._id}
        elif 'resource_id' not in agent_config.get('agent'):
            agent_config['agent']['resource_id'] = device_obj._id


        log.debug("add egg URI if available")
        if agent_obj.driver_uri:
            agent_config['driver_config']['process_type'] = (DriverProcessType.EGG,)
            agent_config['driver_config']['dvr_egg'] = agent_obj.driver_uri
        else:
            agent_config['driver_config']['process_type'] = (DriverProcessType.PYTHON_MODULE,)


        if log.isEnabledFor(logging.INFO):
            tree = self._summarize_children(agent_config)
            log.info("Children of %s are %s", self.agent_instance_obj.name, tree)

        self.generated_config = True

        return agent_config


    def record_launch_parameters(self, agent_config):
        """
        record process id of the launch
        """
        #self.RR2.update(self.agent_instance_obj)

        log.debug('completed agent start')

    def _collect_deployment(self, device_id=None):

        deployment_objs = self.RR2.find_objects(device_id, PRED.hasDeployment, RT.Deployment)

        # find current deployment using time constraints
        current_time =  int( calendar.timegm(time.gmtime()) )

        for d in deployment_objs:
            # find deployment start and end time
            time_constraint = None
            for constraint in d.constraint_list:
                if constraint.type_ == OT.TemporalBounds:
                    if time_constraint:
                        log.warn('deployment %s has more than one time constraint (using first)', d.name)
                    else:
                        time_constraint = constraint
            if time_constraint:
                # a time constraint was provided, check if the current time is in this window
                if int(time_constraint.start_datetime) < current_time < int(time_constraint.end_datetime) :
                    log.debug('_collect_deployment found current deployment start time: %s, end time: %s   current time:  %s    deployment: %s ',
                              time_constraint.start_datetime, time_constraint.end_datetime, current_time, d)
                    return d

        return None

    def _validate_reference_designator(self, port_assignments):
        #validate that each reference designator is valid / parseable
        # otherwise the platform cannot pull out the port number for power mgmt
        if not port_assignments:
            return

        if not isinstance(port_assignments, dict):
            log.error('Deployment for device has invalid port assignments.  device id: %s ', self._get_device()._id)
            return

        for device_id, platform_port in port_assignments.iteritems():
            if platform_port.type_ != OT.PlatformPort:
                log.error('Deployment for device has invalid port assignments for device.  device id: %s', device_id)
            ooi_rd = OOIReferenceDesignator(platform_port.reference_designator)
            if ooi_rd.error:
                log.error('Agent configuration includes a invalid reference designator for a device in this deployment.  device id: %s  reference designator: %s', device_id, platform_port.reference_designator)

        return

    def _serialize_port_assigments(self, port_assignments=None):
        serializer = IonObjectSerializer()
        serialized_port_assignments = {}
        if isinstance(port_assignments, dict):
            for device_id, platform_port in port_assignments.iteritems():
                flatpp = serializer.serialize(platform_port)
                serialized_port_assignments[device_id] = flatpp

        return serialized_port_assignments

    def _collect_agent_instance_associations(self):
        """
        Collect related resources to this agent instance

        Returns a dict of objects necessary to start this instance, keyed on the values of self._lookup_means()
            PRED.hasAgentInstance   -> device_obj
            PRED.hasModel           -> model_obj
            PRED.hasAgentDefinition -> agent_obj
            RT.ProcessDefinition    -> process_def_obj

        """
        assert self.agent_instance_obj

        lookup_means = self._lookup_means()

        assert lookup_means
        assert PRED.hasAgentInstance in lookup_means
        assert PRED.hasModel in lookup_means
        assert PRED.hasAgentDefinition in lookup_means
        #assert PRED.hasProcessDefinition in lookup_means

        lu = lookup_means

        ret = {}

        log.debug("retrieve the associated device")
        res_types = lu[PRED.hasAgentInstance]
        if not hasattr(res_types, "__iter__"):
            res_types = [res_types]

        device_obj = None
        for res_type in res_types:
            try:
                device_obj = self.RR2.find_subject(subject_type=res_type,
                                                   predicate=PRED.hasAgentInstance,
                                                   object=self.agent_instance_obj._id)
                break
            except NotFound:
                pass
        if not device_obj:
            raise NotFound("Could not find a Device for AgentInstance %s" % self.agent_instance_obj._id)

        ret[lu[PRED.hasAgentInstance]] = device_obj   # Note: can be a tuple key
        device_id = device_obj._id

        log.debug("%s '%s' connected to %s '%s' (L4-CI-SA-RQ-363)",
                  lu[PRED.hasAgentInstance],
                  str(device_id),
                  self.agent_instance_obj.type_,
                  self.agent_instance_obj._id)

#        log.debug("retrieve the model associated with the device")
#        model_obj = self.RR2.find_object(subject=device_id,
#                                         predicate=PRED.hasModel,
#                                         object_type=lu[PRED.hasModel])
#
#        ret[lu[PRED.hasModel]] = model_obj
#        model_id = model_obj

        #retrive the stream info for this model
        #todo: add stream info to the platform model create
        #        streams_dict = platform_models_objs[0].custom_attributes['streams']
        #        if not streams_dict:
        #            raise BadRequest("Device model does not contain stream configuation used in launching the agent. Model: '%s", str(platform_models_objs[0]) )
        #TODO: get the agent from the instance not from the model!!!!!!!
        log.debug("retrieve the agent associated with the model")
        agent_obj = self.RR2.find_object(subject=self.agent_instance_obj._id,
                                         predicate=PRED.hasAgentDefinition,
                                         object_type=lu[PRED.hasAgentDefinition])

        ret[lu[PRED.hasAgentDefinition]] = agent_obj
        agent_id = agent_obj._id

        if not agent_obj.stream_configurations:
            raise BadRequest("Agent '%s' does not contain stream configuration used in launching" %
                             str(agent_obj) )

        log.debug("retrieve the process definition associated with this agent")
        process_def_obj = self.RR2.find_object(subject=agent_id,
                                               predicate=PRED.hasProcessDefinition,
                                               object_type=RT.ProcessDefinition)


        ret[RT.ProcessDefinition] = process_def_obj

        #retrieve the output products
        data_product_objs = self.RR2.find_objects(device_id, PRED.hasOutputProduct, RT.DataProduct, id_only=False)
        ret[RT.DataProduct] = data_product_objs

        if not data_product_objs:
            raise NotFound("No output Data Products attached to this Device " + str(device_id))

        #retrieve the streams assoc with each defined output product
        for data_product_obj in data_product_objs:
            product_id = data_product_obj._id
            try:
                self.RR2.find_stream_id_of_data_product_using_has_stream(product_id)  # check one stream per product
            except NotFound:
                errmsg = "Device '%s' (%s) has data products %s.  Data product '%s' (%s) has no stream ID." % \
                    (device_obj.name,
                     device_obj._id,
                     [dp._id for dp in data_product_objs],
                     data_product_obj.name,
                     product_id)
                raise NotFound(errmsg)

            # some products may not be persisted
            try:
                # check one dataset per product
                self.RR2.find_dataset_id_of_data_product_using_has_dataset(product_id)
            except NotFound:
                log.warn("Data product '%s' of device %s ('%s') does not appear to be persisted -- no dataset",
                         product_id, device_obj.name, device_obj._id)

        self.associated_objects = ret


    def _get_device(self):
        self._check_associations()
        return self.associated_objects[self._lookup_means()[PRED.hasAgentInstance]]

#    def _get_model(self):
#        self._check_associations()
#        return self.associated_objects[self._lookup_means()[PRED.hasModel]]

    def _get_agent(self):
        self._check_associations()
        return self.associated_objects[self._lookup_means()[PRED.hasAgentDefinition]]

    def _get_process_definition(self):
        self._check_associations()
        return self.associated_objects[RT.ProcessDefinition]
class AgentConfigurationBuilder(object):

    def __init__(self, clients, RR2=None):
        self.clients = clients
        self.RR2 = RR2

        if self.RR2 is None:
            log.warn("Creating new RR2")
            self.RR2 = EnhancedResourceRegistryClient(self.clients.resource_registry)

        if not isinstance(self.RR2, EnhancedResourceRegistryClient):
            raise AssertionError("Type of self.RR2 is %s not %s" %
                                 (type(self.RR2), type(EnhancedResourceRegistryClient)))

        self.agent_instance_obj = None
        self.associated_objects = None
        self.last_id            = None
        self.will_launch        = False
        self.generated_config   = False

    def _predicates_to_cache(self):
        return [PRED.hasOutputProduct,
                #PRED.hasStream,
                #PRED.hasStreamDefinition,
                PRED.hasAgentInstance,
                PRED.hasAgentDefinition,
                PRED.hasDataset,
                PRED.hasDevice,
                PRED.hasNetworkParent,
                #PRED.hasParameterContext,
                ]

    def _resources_to_cache(self):
        return [#RT.StreamDefinition,
                RT.ParameterDictionary,
                #RT.ParameterContext,
                ]

    def _update_cached_predicates(self):
        # cache some predicates for in-memory lookups
        preds = self._predicates_to_cache()
        log.debug("updating cached predicates: %s" % preds)
        time_caching_start = get_ion_ts()
        for pred in preds:
            log.debug(" - %s", pred)
            self.RR2.cache_predicate(pred)
        time_caching_stop = get_ion_ts()

        total_time = int(time_caching_stop) - int(time_caching_start)

        log.info("Cached %s predicates in %s seconds", len(preds), total_time / 1000.0)

    def _update_cached_resources(self):
        # cache some resources for in-memory lookups
        rsrcs = self._resources_to_cache()
        log.debug("updating cached resources: %s" % rsrcs)
        time_caching_start = get_ion_ts()
        for r in rsrcs:
            log.debug(" - %s", r)
            self.RR2.cache_resources(r)
        time_caching_stop = get_ion_ts()

        total_time = int(time_caching_stop) - int(time_caching_start)

        log.info("Cached %s resource types in %s seconds", len(rsrcs), total_time / 1000.0)

    def _clear_caches(self):
        log.warn("Clearing caches")
        for r in self._resources_to_cache():
            self.RR2.clear_cached_resource(r)

        for p in self._predicates_to_cache():
            self.RR2.clear_cached_predicate(p)
            

    def _lookup_means(self):
        """
        return a dict indicating how various related resources will be looked up

        The dict is keyed on association type:
        PRED.hasAgentInstance -> device type
        PRED.hasModel -> model type
        PRED.hasAgentDefinition -> agent type
        """
        raise NotImplementedError("Extender of class must implement this")

    def _augment_dict(self, title, basedict, newitems):
        for k, v in newitems.iteritems():
            if k in basedict:
                prev_v = basedict[k]
                # just warn if the new value is different
                if v != prev_v:
                    log.warn("Overwriting %s[%s] of '%s' with '%s'", title, k, prev_v, v)
                else:
                    log.debug("Overwriting %s[%s] with same value already assigned '%s'",
                              title, k, v)
            basedict[k] = v

    def _check_associations(self):
        assert self.agent_instance_obj
        assert self.associated_objects

        lookup_means = self._lookup_means()
        assert lookup_means

        # make sure we've picked up the associations we expect
        def check_keys(somekeys):
            for k in somekeys:
                assert k in lookup_means
                assert lookup_means[k] in self.associated_objects

        #check_keys([PRED.hasAgentInstance, PRED.hasModel, PRED.hasAgentDefinition])
        check_keys([PRED.hasAgentInstance, PRED.hasAgentDefinition])
        assert RT.ProcessDefinition in self.associated_objects


    def set_agent_instance_object(self, agent_instance_obj):
        """
        Set the agent instance object that we'll be interacting with

        it may be necessary to set this several times, such as if external operations update the object
        """
        assert agent_instance_obj._id

        if self.last_id != agent_instance_obj._id:
            self.associated_objects = None

        self.agent_instance_obj = agent_instance_obj
        self.last_id = agent_instance_obj._id
        self.generated_config = False

    def prepare(self, will_launch=True):
        """
        Prepare (validate) an agent for launch, fetching all associated resources

        @param will_launch - whether the running status should be checked -- set false if just generating config
        """
        assert self.agent_instance_obj

        if will_launch:
            #if there is an agent pid then assume that a drive is already started
            if self.agent_instance_obj.agent_process_id:
                raise BadRequest("Agent Instance already running for this device pid: %s" %
                                 str(self.agent_instance_obj.agent_process_id))

        # fetch caches just in time
        if any([not self.RR2.has_cached_predicate(x) for x in self._predicates_to_cache()]):
            self._update_cached_predicates()

        if any([not self.RR2.has_cached_resource(x) for x in self._resources_to_cache()]):
            self._update_cached_resources()

        # validate the associations, then pick things up
        self._collect_agent_instance_associations()
        self.will_launch = will_launch
        return self.generate_config()


    def _generate_org_governance_name(self):
        log.debug("_generate_org_governance_name for %s", self.agent_instance_obj.name)
        log.debug("retrieve the Org governance name to which this agent instance belongs")
        try:
            org_obj = self.RR2.find_subject(RT.Org, PRED.hasResource, self.agent_instance_obj._id, id_only=False)
            return org_obj.org_governance_name
        except NotFound:
            return ''
        except:
            raise

    def _generate_device_type(self):
        log.debug("_generate_device_type for %s", self.agent_instance_obj.name)
        return type(self._get_device()).__name__

    def _generate_driver_config(self):
        log.debug("_generate_driver_config for %s", self.agent_instance_obj.name)
        # get default config
        driver_config = self.agent_instance_obj.driver_config

        agent_obj = self._get_agent()

        # Create driver config.
        add_driver_config = {
            'workdir'      : tempfile.gettempdir(),
            'dvr_mod'      : agent_obj.driver_module,
            'dvr_cls'      : agent_obj.driver_class
        }

        self._augment_dict("Agent driver_config", driver_config, add_driver_config)

        return driver_config

    def _get_param_dict_by_name(self, name):
        dict_obj = self.RR2.find_resources_by_name(RT.ParameterDictionary, name)[0]
        parameter_contexts = \
            self.RR2.find_parameter_contexts_of_parameter_dictionary_using_has_parameter_context(dict_obj._id)
        return DatasetManagementService.build_parameter_dictionary(dict_obj, parameter_contexts)

    def _meet_in_the_middle(self,dp_id, pdict_id):
        # Given a pdict_id and a data_product_id find the stream def in the middle
        pdict_stream_defs = self.RR2.find_stream_definition_ids_by_parameter_dictionary_using_has_parameter_dictionary(pdict_id)
        stream_def_id = self.RR2.find_stream_definition_id_of_data_product_using_has_stream_definition(dp_id)
        result = stream_def_id if stream_def_id in pdict_stream_defs else None
        return result


    def _generate_stream_config(self):
        log.debug("_generate_stream_config for %s", self.agent_instance_obj.name)
        dsm = self.clients.dataset_management
        psm = self.clients.pubsub_management

        agent_obj  = self._get_agent()
        device_obj = self._get_device()

        streams_dict = {}
        for stream_cfg in agent_obj.stream_configurations:
            #create a stream def for each param dict to match against the existing data products
            streams_dict[stream_cfg.stream_name] = {'param_dict_name':stream_cfg.parameter_dictionary_name,
                                                    #'stream_def_id':stream_def_id,
                                                    'records_per_granule': stream_cfg.records_per_granule,
                                                    'granule_publish_rate':stream_cfg.granule_publish_rate,
                                                     }

        #retrieve the output products
        device_id = device_obj._id
        data_product_objs = self.RR2.find_data_products_of_instrument_device_using_has_output_product(device_id)

        stream_config = {}
        for d in data_product_objs:
            stream_def_id = self.RR2.find_stream_definition_id_of_data_product_using_has_stream_definition(d._id)
            for model_stream_name, stream_info_dict  in streams_dict.items():
                # read objects from cache to be compared
                pdict = self.RR2.find_resource_by_name(RT.ParameterDictionary, stream_info_dict.get('param_dict_name'))
                stream_def_id = self._meet_in_the_middle(d._id, pdict._id)

                if stream_def_id:
                    #model_param_dict = self.RR2.find_resources_by_name(RT.ParameterDictionary,
                    #                                         stream_info_dict.get('param_dict_name'))[0]
                    #model_param_dict = self._get_param_dict_by_name(stream_info_dict.get('param_dict_name'))
                    #stream_route = self.RR2.read(product_stream_id).stream_route
                    product_stream_id = self.RR2.find_stream_id_of_data_product_using_has_stream(d._id)
                    stream_def = psm.read_stream_definition(stream_def_id)
                    stream_route = psm.read_stream_route(stream_id=product_stream_id)
                    
                    from pyon.core.object import IonObjectSerializer
                    stream_def_dict = IonObjectSerializer().serialize(stream_def)
                    sdtype = stream_def_dict.pop('type_')

                    if model_stream_name in stream_config:
                        log.warn("Overwiting stream_config[%s]", model_stream_name)

                    stream_config[model_stream_name] = {'routing_key'           : stream_route.routing_key,
                                                        'stream_id'             : product_stream_id,
                                                        'stream_definition_ref' : stream_def_id,
                                                        'stream_def_dict'       : stream_def_dict,
                                                        'exchange_point'        : stream_route.exchange_point,
                                                        'parameter_dictionary'  : stream_def.parameter_dictionary,
                                                        'records_per_granule'   : stream_info_dict.get('records_per_granule'),
                                                        'granule_publish_rate'  : stream_info_dict.get('granule_publish_rate'),
                    }

        log.debug("Stream config generated")
        log.trace("generate_stream_config: %s", str(stream_config) )
        return stream_config

    def _generate_agent_config(self):
        log.debug("_generate_agent_config for %s", self.agent_instance_obj.name)
        # should override this
        return {}

    def _generate_alerts_config(self):
        log.debug("_generate_alerts_config for %s", self.agent_instance_obj.name)
        # should override this
        return self.agent_instance_obj.alerts

    def _generate_startup_config(self):
        log.debug("_generate_startup_config for %s", self.agent_instance_obj.name)
        # should override this
        return {}

    def _generate_children(self):
        log.debug("_generate_children for %s", self.agent_instance_obj.name)
        # should override this
        return {}

    def _generate_skeleton_config_block(self):
        log.info("Generating skeleton config block for %s", self.agent_instance_obj.name)

        # should override this
        agent_config = self.agent_instance_obj.agent_config

        # Create agent_ config.
        agent_config['instance_name']       = self.agent_instance_obj.name
        agent_config['org_governance_name'] = self._generate_org_governance_name()
        agent_config['device_type']         = self._generate_device_type()
        agent_config['driver_config']       = self._generate_driver_config()
        agent_config['stream_config']       = self._generate_stream_config()
        agent_config['agent']               = self._generate_agent_config()
        agent_config['aparam_alerts_config'] = self._generate_alerts_config()
        agent_config['startup_config']      = self._generate_startup_config()
        agent_config['children']            = self._generate_children()

        log.info("DONE generating skeleton config block for %s", self.agent_instance_obj.name)

        return agent_config


    def _summarize_children(self, config_dict):
        ret = dict([(v['instance_name'], self._summarize_children(v))
                                for k, v in config_dict["children"].iteritems()])
        #agent_config['agent']['resource_id']
        return ret

    def generate_config(self):
        """
        create the generic parts of the configuration including resource_id, egg_uri, and org
        """
        if self.generated_config:
            log.warn("Generating config again for the same Instance object (%s)", self.agent_instance_obj.name)

        self._check_associations()

        agent_config = self._generate_skeleton_config_block()

        device_obj = self._get_device()
        agent_obj  = self._get_agent()

        log.debug("complement agent_config with resource_id")
        if 'agent' not in agent_config:
            agent_config['agent'] = {'resource_id': device_obj._id}
        elif 'resource_id' not in agent_config.get('agent'):
            agent_config['agent']['resource_id'] = device_obj._id


        log.debug("add egg URI if available")
        if agent_obj.driver_uri:
            agent_config['driver_config']['process_type'] = (DriverProcessType.EGG,)
            agent_config['driver_config']['dvr_egg'] = agent_obj.driver_uri
        else:
            agent_config['driver_config']['process_type'] = (DriverProcessType.PYTHON_MODULE,)


        if log.isEnabledFor(logging.INFO):
            tree = self._summarize_children(agent_config)
            log.info("Children of %s are %s", self.agent_instance_obj.name, tree)

        self.generated_config = True

        return agent_config



    def record_launch_parameters(self, agent_config, process_id):
        """
        record process id of the launch
        """

        log.debug("add the process id and update the resource")
        self.agent_instance_obj.agent_config = agent_config
        self.agent_instance_obj.agent_process_id = process_id
        self.RR2.update(self.agent_instance_obj)

        log.debug('completed agent start')

        return process_id



    def _collect_agent_instance_associations(self):
        """
        Collect related resources to this agent instance

        Returns a dict of objects necessary to start this instance, keyed on the values of self._lookup_means()
            PRED.hasAgentInstance   -> device_obj
            PRED.hasModel           -> model_obj
            PRED.hasAgentDefinition -> agent_obj
            RT.ProcessDefinition    -> process_def_obj

        """
        assert self.agent_instance_obj

        lookup_means = self._lookup_means()

        assert lookup_means
        assert PRED.hasAgentInstance in lookup_means
        assert PRED.hasModel in lookup_means
        assert PRED.hasAgentDefinition in lookup_means
        #assert PRED.hasProcessDefinition in lookup_means

        lu = lookup_means

        ret = {}

        log.debug("retrieve the associated device")
        device_obj = self.RR2.find_subject(subject_type=lu[PRED.hasAgentInstance],
                                           predicate=PRED.hasAgentInstance,
                                           object=self.agent_instance_obj._id)

        ret[lu[PRED.hasAgentInstance]]= device_obj
        device_id = device_obj._id

        log.debug("%s '%s' connected to %s '%s' (L4-CI-SA-RQ-363)",
                  lu[PRED.hasAgentInstance],
                  str(device_id),
                  type(self.agent_instance_obj).__name__,
                  str(self.agent_instance_obj._id))

#        log.debug("retrieve the model associated with the device")
#        model_obj = self.RR2.find_object(subject=device_id,
#                                         predicate=PRED.hasModel,
#                                         object_type=lu[PRED.hasModel])
#
#        ret[lu[PRED.hasModel]] = model_obj
#        model_id = model_obj

        #retrive the stream info for this model
        #todo: add stream info to the platofrom model create
        #        streams_dict = platform_models_objs[0].custom_attributes['streams']
        #        if not streams_dict:
        #            raise BadRequest("Device model does not contain stream configuation used in launching the agent. Model: '%s", str(platform_models_objs[0]) )
        #TODO: get the agent from the instance not from the model!!!!!!!
        log.debug("retrieve the agent associated with the model")
        agent_obj = self.RR2.find_object(subject=self.agent_instance_obj._id,
                                         predicate=PRED.hasAgentDefinition,
                                         object_type=lu[PRED.hasAgentDefinition])

        ret[lu[PRED.hasAgentDefinition]] = agent_obj
        agent_id = agent_obj._id

        if not agent_obj.stream_configurations:
            raise BadRequest("Agent '%s' does not contain stream configuration used in launching" %
                             str(agent_obj) )

        log.debug("retrieve the process definition associated with this agent")
        process_def_obj = self.RR2.find_object(subject=agent_id,
                                               predicate=PRED.hasProcessDefinition,
                                               object_type=RT.ProcessDefinition)


        ret[RT.ProcessDefinition] = process_def_obj

        #retrieve the output products
        data_product_ids = self.RR2.find_objects(device_id, PRED.hasOutputProduct, RT.DataProduct, id_only=True)

        if not data_product_ids:
            raise NotFound("No output Data Products attached to this Device " + str(device_id))

        #retrieve the streams assoc with each defined output product
        for product_id in data_product_ids:
            self.RR2.find_stream_id_of_data_product_using_has_stream(product_id)  # check one stream per product
            self.RR2.find_dataset_id_of_data_product_using_has_dataset(product_id) # check one dataset per product

        self.associated_objects = ret


    def _get_device(self):
        self._check_associations()
        return self.associated_objects[self._lookup_means()[PRED.hasAgentInstance]]

#    def _get_model(self):
#        self._check_associations()
#        return self.associated_objects[self._lookup_means()[PRED.hasModel]]

    def _get_agent(self):
        self._check_associations()
        return self.associated_objects[self._lookup_means()[PRED.hasAgentDefinition]]

    def _get_process_definition(self):
        self._check_associations()
        return self.associated_objects[RT.ProcessDefinition]
        def freeze():

            if isinstance(resource_registry_client, EnhancedResourceRegistryClient):
                RR2 = resource_registry_client
            else:
                RR2 = EnhancedResourceRegistryClient(resource_registry_client)

            for p in predicate_list:
                if not RR2.has_cached_prediate(p):
                    RR2.cache_predicate(p)


            def get_related_resources_partial_fn(predicate_dictionary, resource_whitelist):
                """
                This function generates a resource crawler from 2 data structures representing desired crawl behavior

                The predicate dictionary is keyed on a predicate type, whose value is a 2-tuple of booleans
                  the first boolean is whether to crawl subject-object, the second boolean for object-subject
                  For example: dict([(PRED.hasModel, (False, True)]) would generate a crawler that could find
                               platforms or instruments with a given model

                The resource whitelist is a list of resource types that will be crawled.

                The return value of this function is a function that accepts a resource id and returns a list
                  of associations related (based on crawl behavior)
                """
                log.trace("get_related_resources_partial_fn predicate_dict=%s rsrc_whitelist=%s",
                          predicate_dictionary,
                          resource_whitelist)

                # assertions on data types
                assert type({}) == type(predicate_dictionary)
                for v in predicate_dictionary.values():
                    assert type((True, True)) == type(v)
                assert type([]) == type(resource_whitelist)

                for rt in resource_whitelist:
                    RR2.cache_resources(rt)

                def lookup_fn(resource_id):
                    """
                    return a dict of related resources as dictated by the pred dict and whitelist
                     - the key is the next resource id to crawl
                     - the value is the entire association
                    """
                    retval = {}

                    sto_match = lambda assn: assn.s == resource_id and assn.ot in resource_whitelist
                    ots_match = lambda assn: assn.o == resource_id and assn.st in resource_whitelist
                    for p, (search_sto, search_ots) in predicate_dictionary.iteritems():
                        if search_sto:
                            for a in RR2.filter_cached_associations(p, sto_match):
                                log.trace("lookup_fn matched %s object", a.ot)
                                retval[a.o] = a
                        if search_ots:
                            for a in RR2.filter_cached_associations(p, ots_match):
                                log.trace("lookup_fn matched %s subject", a.st)
                                retval[a.s] = a


                    return retval


                def get_related_resources_h(accum, input_resource_id, recursion_limit):
                    """
                    This is a recursive helper function that does the work of crawling for related resources

                    The accum is a tuple: (set of associations that are deemed "Related", set of "seen" resources)

                    The input resource id is the current resource being crawled

                    The recursion limit decrements with each recursive call, ending at 0.  So -1 for infinity.

                    The return value is a list of associations
                    """
                    if 0 == recursion_limit:
                        return accum

                    if -1000 > recursion_limit:
                        log.warn("Terminating related resource recursion, hit -1000")
                        return accum

                    acc, seen = accum

                    matches = lookup_fn(input_resource_id)
                    log.trace("get_related_resources_h got matches %s",
                              [dict((k, "%s %s %s" % (a.st, a.p, a.ot)) for k, a in matches.iteritems())])

                    unseen = set(matches.keys()) - seen
                    seen.add(input_resource_id)
                    acc  = acc  | set(matches.values())

                    #if log.isEnabledFor(logging.TRACE):
                    #    summary = {}
                    #    for a in acc:
                    #        label = "%s %s %s" % (a.st, a.p, a.ot)
                    #        if not label in summary: summary[label] = 0
                    #        summary[label] += 1
                    #    log.trace("acc2 is now %s", ["%s x%d" % (k, v) for k, v in summary.iteritems()])

                    def looper(acc2, input_rsrc_id):
                        return get_related_resources_h(acc2, input_rsrc_id, recursion_limit - 1)

                    h_ret = reduce(looper, unseen, (acc, seen))
                    #h_ret = reduce(looper, unseen, (acc, seen))
                    #(h_ret_acc, h_ret_seen) = h_ret
                    #log.trace("h_ret is %s", ["%s %s %s" % (a.st, a.p, a.ot) for a in h_ret_acc])
                    return h_ret


                def get_related_resources_fn(input_resource_id, recursion_limit=1024):
                    """
                    This is the function that finds related resources.

                    input_resource_id and recursion_limit are self explanatory

                    The return value is a list of associations.
                    """
                    retval, _ = get_related_resources_h((set([]), set([])), input_resource_id, recursion_limit)
                    log.trace("final_ret is %s", ["%s %s %s" % (a.st, a.p, a.ot) for a in retval])
                    return list(retval)

                return get_related_resources_fn # retval of get_related_resources_partial_fn

            return get_related_resources_partial_fn # retval of freeze()
class TestEnhancedResourceRegistryClient(PyonTestCase):
    def setUp(self):
        self.rr = Mock()
        self.RR2 = EnhancedResourceRegistryClient(self.rr)

    def sample_resource(self):
        return any_old(RT.InstrumentDevice)

    def test_init(self):
        pass

    def test_create(self):
        """
        test resource creation in normal case
        """
        # get objects
        good_sample_resource = self.sample_resource()

        #configure Mock
        self.rr.create.return_value = ('111', 'bla')
        self.rr.find_resources.return_value = ([], [])

        sample_resource_id = self.RR2.create(good_sample_resource,
                                             RT.InstrumentDevice)

        self.rr.create.assert_called_once_with(good_sample_resource)
        self.assertEqual(sample_resource_id, '111')

    def test_create_bad_wrongtype(self):
        """
        test resource creation failure for wrong type
        """
        # get objects

        bad_sample_resource = any_old(RT.PlatformDevice)
        delattr(bad_sample_resource, "name")

        #configure Mock
        self.rr.create.return_value = ('111', 'bla')
        self.rr.find_resources.return_value = ([], [])

        self.assertRaises(BadRequest, self.RR2.create, bad_sample_resource,
                          RT.InstrumentDevice)

    def test_create_bad_noname(self):
        """
        test resource creation failure for no name
        """
        # get objects

        bad_sample_resource = self.sample_resource()
        delattr(bad_sample_resource, "name")

        #configure Mock
        self.rr.create.return_value = ('111', 'bla')
        self.rr.find_resources.return_value = ([], [])

        self.assertRaises(BadRequest, self.RR2.create, bad_sample_resource,
                          RT.InstrumentDevice)

#    def test_create_bad_dupname(self):
#        """
#        test resource creation failure for duplicate name
#        """
#        # get objects
#
#        bad_sample_resource = self.sample_resource()
#        #really, the resource doesn't matter; it's the retval from find that matters
#
#        #configure Mock
#        self.rr.create.return_value = ('111', 'bla')
#        self.rr.find_resources.return_value = ([0], [0])
#
#        self.assertRaises(BadRequest, self.RR2.create, bad_sample_resource, RT.InstrumentDevice)
#

    def test_read(self):
        """
        test resource read (passthru)
        """
        # get objects
        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret

        response = self.RR2.read("111", RT.InstrumentDevice)
        self.rr.read.assert_called_once_with("111")
        self.assertEqual(response, myret)
        #self.assertDictEqual(response.__dict__,
        #                     self.sample_resource().__dict__)

    def test_read_bad_wrongtype(self):
        """
        test resource read (passthru)
        """
        # get objects
        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret

        self.assertRaises(BadRequest, self.RR2.read, "111", RT.PlatformDevice)
        self.rr.read.assert_called_once_with("111")

    def test_update(self):
        """
        test resource update in normal case
        """
        # get objects

        good_sample_resource = self.sample_resource()
        setattr(good_sample_resource, "_id", "111")

        #configure Mock
        self.rr.update.return_value = ('111', 'bla')
        self.rr.find_resources.return_value = ([], [])

        self.RR2.update(good_sample_resource, RT.InstrumentDevice)

        self.rr.update.assert_called_once_with(good_sample_resource)

    def test_update_bad_wrongtype(self):
        """
        test update failure due to duplicate name
        """
        # get objects

        bad_sample_resource = self.sample_resource()

        self.assertRaises(BadRequest, self.RR2.update, bad_sample_resource,
                          RT.PlatformDevice)

#
#    def test_update_bad_dupname(self):
#        """
#        test update failure due to duplicate name
#        """
#        # get objects
#
#        bad_sample_resource = self.sample_resource()
#        setattr(bad_sample_resource, "_id", "111")
#
#        self.rr.find_resources.return_value = ([0], [0])
#        self.assertRaises(BadRequest, self.RR2.update, bad_sample_resource, RT.InstrumentDevice)

    def test_update_bad_noid(self):
        """
        test update failure due to duplicate name
        """
        # get objects

        bad_sample_resource = self.sample_resource()

        self.rr.find_resources.return_value = ([0], [0])
        self.assertRaises(BadRequest, self.RR2.update, bad_sample_resource,
                          RT.InstrumentDevice)

    def test_retire(self):
        """
        test retire
        """
        # get objects

        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret
        self.rr.delete.return_value = None
        self.rr.retire.return_value = None

        try:
            self.RR2.retire("111", RT.InstrumentDevice)
        except TypeError as te:
            # for logic tests that run into mock trouble
            if "'Mock' object is not iterable" != te.message:
                raise te
            else:
                raise SkipTest("Must test this with INT test")
        except Exception as e:
            raise e

        #self.rr.read.assert_called_with("111", "")
        self.rr.retire.assert_called_once_with("111")

    def test_retire_bad_wrongtype(self):
        """
        test resource read (passthru)
        """
        # get objects
        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret

        self.assertRaises(BadRequest, self.RR2.retire, "111",
                          RT.PlatformDevice)
        self.rr.read.assert_called_once_with("111")

    def test_pluck_delete(self):
        """
        test delete
        """
        # get objects

        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret
        self.rr.delete.return_value = None
        self.rr.find_resources.return_value = None
        self.rr.find_objects.return_value = (["2"], ["2"])
        self.rr.find_subjects.return_value = (["3"], ["3"])

        self.RR2.pluck_delete("111", RT.InstrumentDevice)

        self.rr.delete.assert_called_once_with("111")

    def test_advance_lcs(self):
        """
        call RR when the transition ISN'T retire
        """
        self.RR2.advance_lcs("111", LCE.PLAN)
        self.rr.execute_lifecycle_transition.assert_called_once_with(
            resource_id="111", transition_event=LCE.PLAN)

        self.RR2.advance_lcs("222", LCE.RETIRE)
        self.rr.retire.assert_called_once_with("222")

    def test_delete_association(self):
        self.rr.get_association.return_value = "111"
        self.RR2.delete_association("a", "b", "c")
        self.rr.delete_association.assert_called_once_with("111")

    def test_delete_all_object_associations(self):
        self.rr.find_associations.return_value = ["111"]
        self.RR2.delete_object_associations("x")
        self.rr.delete_association.assert_called_once_with("111")

    def test_delete_all_subject_associations(self):
        self.rr.find_associations.return_value = ["111"]
        self.RR2.delete_subject_associations("x")
        self.rr.delete_association.assert_called_once_with("111")

    def test_pluck(self):
        self.rr.find_subjects.return_value = (["111"], ["aaa"])
        self.rr.find_objects.return_value = (["222"], ["bbb"])
        self.RR2.pluck("x")
        #self.rr.delete_association.assert_called_with("bbb")
        self.rr.delete_association.assert_called_with("aaa")
        self.assertEqual(self.rr.delete_association.call_count, 2)

    def test_find_objects_using_id(self):
        self.tbase_find_objects("x_id")

    def test_find_objects_using_ionobj(self):
        obj = any_old(RT.InstrumentDevice)
        setattr(obj, "_id", "foo_id")
        self.tbase_find_objects(obj)

    def test_find_objects_using_junk(self):
        self.tbase_find_objects(1)

    def tbase_find_objects(self, sample_obj):
        """
        test all 8 flavors of find objects: return IonObjects/ids, return single/multiple, use predicate/no-predicate
        """
        def rst():
            self.rr.find_objects.reset_mock()
            self.rr.find_objects.return_value = ([], [])
            self.assertEqual(0, self.rr.find_subjects.call_count)

        def rst1():
            self.rr.find_objects.reset_mock()
            self.rr.find_objects.return_value = (["x"], ["x"])
            self.assertEqual(0, self.rr.find_subjects.call_count)

        def rst2():
            self.rr.find_objects.reset_mock()
            self.rr.find_objects.return_value = (["x", "y"], ["z", "k"])
            self.assertEqual(0, self.rr.find_subjects.call_count)

        x = sample_obj
        xx = x
        if hasattr(x, "_id"):
            xx = x._id

        # find none
        rst()
        self.RR2.find_instrument_models_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=False)

        rst()
        self.assertRaises(
            NotFound, self.RR2.
            find_instrument_model_of_instrument_device_using_has_model, x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=False)

        rst()
        self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(
            x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=True)

        rst()
        self.assertRaises(
            NotFound, self.RR2.
            find_instrument_model_id_of_instrument_device_using_has_model, x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=True)

        # find one
        rst1()
        self.RR2.find_instrument_models_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=False)

        rst1()
        self.RR2.find_instrument_model_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=False)

        rst1()
        self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(
            x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=True)

        rst1()
        self.RR2.find_instrument_model_id_of_instrument_device_using_has_model(
            x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=True)

        # find multiples
        rst2()
        self.RR2.find_instrument_models_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=False)

        rst2()
        self.assertRaises(
            Inconsistent, self.RR2.
            find_instrument_model_of_instrument_device_using_has_model, x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=False)

        rst2()
        self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(
            x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=True)

        rst2()
        self.assertRaises(
            Inconsistent, self.RR2.
            find_instrument_model_id_of_instrument_device_using_has_model, x)
        self.rr.find_objects.assert_called_once_with(
            subject=xx,
            predicate=PRED.hasModel,
            object_type=RT.InstrumentModel,
            id_only=True)

#        # find using
#        rst2()
#        self.RR2.find_instrument_models_of_instrument_device_using_has_model(x)
#        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)
#
#        rst2()
#        self.assertRaises(Inconsistent, self.RR2.find_instrument_model_of_instrument_device_using_has_model, x)
#        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)
#
#        rst2()
#        self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(x)
#        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)
#
#        rst2()
#        self.assertRaises(Inconsistent, self.RR2.find_instrument_model_id_of_instrument_device_using_has_model, x)
#        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)

    def test_find_subjects_using_id(self):
        self.tbase_find_subjects("x_id")

    def test_find_subjects_using_ionobj(self):
        obj = any_old(RT.InstrumentDevice)
        setattr(obj, "_id", "foo_id")
        self.tbase_find_subjects(obj)

    def test_find_subjects_using_junk(self):
        self.tbase_find_subjects(1)

    def tbase_find_subjects(self, sample_obj):
        """
        test all 8 flavors of find subjects: return IonObjects/ids, return single/multiple, use predicate/no-predicate
        """
        def rst():
            self.rr.find_subjects.reset_mock()
            self.rr.find_subjects.return_value = ([], [])
            self.assertEqual(0, self.rr.find_objects.call_count)

        def rst1():
            self.rr.find_subjects.reset_mock()
            self.rr.find_subjects.return_value = (["x"], ["x"])
            self.assertEqual(0, self.rr.find_objects.call_count)

        def rst2():
            self.rr.find_subjects.reset_mock()
            self.rr.find_subjects.return_value = (["x", "y"], ["z", "k"])
            self.assertEqual(0, self.rr.find_objects.call_count)

        x = sample_obj
        xx = x
        if hasattr(x, "_id"):
            xx = x._id

        # find none
        rst()
        self.RR2.find_instrument_devices_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=False)

        rst()
        self.assertRaises(
            NotFound, self.RR2.
            find_instrument_device_by_instrument_model_using_has_model, x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=False)

        rst()
        self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(
            x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=True)

        rst()
        self.assertRaises(
            NotFound, self.RR2.
            find_instrument_device_id_by_instrument_model_using_has_model, x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=True)

        # find 1
        rst1()
        self.RR2.find_instrument_devices_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=False)

        rst1()
        self.RR2.find_instrument_device_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=False)

        rst1()
        self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(
            x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=True)

        rst1()
        self.RR2.find_instrument_device_id_by_instrument_model_using_has_model(
            x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=True)

        # find multiple
        rst2()
        self.RR2.find_instrument_devices_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=False)

        rst2()
        self.assertRaises(
            Inconsistent, self.RR2.
            find_instrument_device_by_instrument_model_using_has_model, x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=False)

        rst2()
        self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(
            x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=True)

        rst2()
        self.assertRaises(
            Inconsistent, self.RR2.
            find_instrument_device_id_by_instrument_model_using_has_model, x)
        self.rr.find_subjects.assert_called_once_with(
            object=xx,
            predicate=PRED.hasModel,
            subject_type=RT.InstrumentDevice,
            id_only=True)

#
#        # find using
#        rst2()
#        self.RR2.find_instrument_devices_by_instrument_model_using_has_model(x)
#        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)
#
#        rst2()
#        self.assertRaises(Inconsistent, self.RR2.find_instrument_device_by_instrument_model_using_has_model, x)
#        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)
#
#        rst2()
#        self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(x)
#        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)
#
#        rst2()
#        self.assertRaises(Inconsistent, self.RR2.find_instrument_device_id_by_instrument_model_using_has_model, x)
#        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)

    def test_assign_unassign(self):
        """
        test all flavors of assign and unassign: with/without predicates
        """
        x = "x_id"
        y = "y_id"
        self.RR2.assign_instrument_model_to_instrument_device_with_has_model(
            y, x)
        self.rr.create_association.assert_called_once_with(x, PRED.hasModel, y)

        self.rr.get_association.return_value = "zzz"
        self.RR2.unassign_instrument_model_from_instrument_device_with_has_model(
            y, x)
        self.rr.delete_association.assert_called_once_with("zzz")

        self.rr.create_association.reset_mock()
        self.RR2.assign_data_product_to_data_process_with_has_output_product(
            y, x)
        self.rr.create_association.assert_called_once_with(
            x, PRED.hasOutputProduct, y)

        self.rr.delete_association.reset_mock()
        self.rr.get_association.reset_mock()
        self.rr.get_association.return_value = "aaa"
        self.RR2.unassign_data_product_from_data_process_with_has_output_product(
            y, x)
        self.rr.delete_association.assert_called_once_with("aaa")

    def test_assign_single_object(self):
        x = "x_id"
        y = "y_id"

        def rst():
            self.rr.find_objects.reset_mock()
            self.rr.get_association.reset_mock()

        rst()
        self.rr.find_objects.return_value = ([], [])
        self.RR2.assign_one_instrument_model_to_instrument_device_with_has_model(
            y, x)
        self.rr.create_association.assert_called_once_with(x, PRED.hasModel, y)

        rst()
        self.rr.find_objects.return_value = (["a", "b"], ["c", "d"])
        self.assertRaises(
            Inconsistent, self.RR2.
            assign_one_instrument_model_to_instrument_device_with_has_model, y,
            x)

        rst()
        self.rr.find_objects.return_value = (["a"], ["b"])
        self.rr.get_association.return_value = "yay"
        self.RR2.assign_one_instrument_model_to_instrument_device_with_has_model(
            y, x)

        rst()
        self.rr.find_objects.return_value = (["a"], ["b"])
        self.rr.get_association.side_effect = NotFound("")
        self.assertRaises(
            BadRequest, self.RR2.
            assign_one_instrument_model_to_instrument_device_with_has_model, y,
            x)

    def test_assign_single_subject(self):
        x = "x_id"
        y = "y_id"

        def rst():
            self.rr.find_subjects.reset_mock()
            self.rr.get_association.reset_mock()

        rst()
        self.rr.find_subjects.return_value = ([], [])
        self.RR2.assign_instrument_device_to_one_instrument_site_with_has_device(
            y, x)
        self.rr.create_association.assert_called_once_with(
            x, PRED.hasDevice, y)

        rst()
        self.rr.find_subjects.return_value = (["a", "b"], ["c", "d"])
        self.assertRaises(
            Inconsistent, self.RR2.
            assign_instrument_device_to_one_instrument_site_with_has_device, y,
            x)

        rst()
        self.rr.find_subjects.return_value = (["a"], ["b"])
        self.rr.get_association.return_value = "yay"
        self.RR2.assign_instrument_device_to_one_instrument_site_with_has_device(
            y, x)

        rst()
        self.rr.find_subjects.return_value = (["a"], ["b"])
        self.rr.get_association.side_effect = NotFound("")
        self.assertRaises(
            BadRequest, self.RR2.
            assign_instrument_device_to_one_instrument_site_with_has_device, y,
            x)

    def test_bad_dynamics(self):
        x = "x_id"
        self.RR2.assign_foo_to_bar(x)
        self.rr.assign_foo_to_bar.assert_called_once_with(x)

        self.assertRaises(
            BadRequest, getattr, self.RR2,
            "find_instrument_model_of_instrument_device_using_has_site")
        self.assertRaises(
            BadRequest, getattr, self.RR2,
            "find_instrument_model_of_instrument_device_using_has_banana")
        #self.assertRaises(BadRequest, getattr, self.RR2, "find_data_product_of_data_process")

        self.RR2.find_sensor_model_by_data_product(x)
        self.rr.find_sensor_model_by_data_product.assert_called_once_with(x)

    def test_cached_predicate_search(self):
        d = "d_id"
        m = "m_id"
        x = "x_id"

        good_assn = DotDict(s=d,
                            st=RT.InstrumentDevice,
                            p=PRED.hasModel,
                            o=m,
                            ot=RT.InstrumentModel)
        bad_assn = DotDict(s=d,
                           st=RT.PlatformDevice,
                           p=PRED.hasModel,
                           o=m,
                           ot=RT.PlatformModel)

        self.rr.find_associations.return_value = [good_assn, bad_assn]

        self.RR2.cache_predicate(PRED.hasModel)

        self.assertTrue(self.RR2.has_cached_predicate(PRED.hasModel))
        self.rr.find_associations.assert_called_once_with(
            predicate=PRED.hasModel, id_only=False)

        # object searches that should return 0, 0, 1 results
        results = self.RR2.find_objects(x, PRED.hasModel, RT.InstrumentModel,
                                        True)
        self.assertEqual([], results)
        results = self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(
            x)
        self.assertEqual([], results)
        results = self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(
            d)
        self.assertEqual([m], results)

        self.assertEqual(0, self.rr.find_objects.call_count)

        # subject searches that should return 0, 0, 1 results
        results = self.RR2.find_subjects(RT.InstrumentDevice, PRED.hasModel, x,
                                         True)
        self.assertEqual([], results)
        results = self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(
            x)
        self.assertEqual([], results)
        results = self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(
            m)
        self.assertEqual([d], results)

        self.assertEqual(0, self.rr.find_subjects.call_count)
示例#5
0
class RollXBuilder(object):
    """
    for rollups and rolldowns
    """
    def __init__(self, process=None):
        """
        the process should be the "self" of a service instance
        """
        assert process
        self.process = process
        self.RR2 = EnhancedResourceRegistryClient(
            self.process.clients.resource_registry)

    def get_toplevel_platformsite(self, site_id):
        if not self.RR2.has_cached_predicate(PRED.hasSite):
            self.RR2.cache_predicate(PRED.hasSite)

        parent_ids = self.RR2.find_platform_site_ids_by_platform_site_using_has_site(
            site_id)

        if 0 == len(parent_ids):
            return site_id  # assume this to be the top level

        return self.get_toplevel_platformsite(parent_ids[0])

    def get_parent_network_nodes(self, site_id):
        """
        return the parent nodes of this network node, including the given node
        """
        if not self.RR2.has_cached_predicate(PRED.hasNetworkParent):
            self.RR2.cache_predicate(PRED.hasNetworkParent)

        def get_h(acc, some_id):
            acc.append(some_id)
            parent_ids = self.RR2.find_platform_device_ids_of_platform_device_using_has_network_parent(
                site_id)
            if 0 == len(parent_ids):
                return acc
            return get_h(acc, parent_ids[0])

        return get_h([], site_id)

    def get_toplevel_network_node(self, device_id):
        if not self.RR2.has_cached_predicate(PRED.hasNetworkParent):
            self.RR2.cache_predicate(PRED.hasNetworkParent)

        parent_ids = self.RR2.find_platform_device_ids_of_platform_device_using_has_network_parent(
            device_id)

        if 0 == len(parent_ids):
            # it can only be the network parent if it has this association
            if 0 < len(
                    self.RR2.
                    find_platform_device_ids_by_platform_device_using_has_network_parent(
                        device_id)):
                return device_id  # assume this to be top level
            else:
                return None

        return self.get_toplevel_network_node(parent_ids[0])

    def get_site_hierarchy(self, site_id, site_val_fn):
        """
        return (child_sites, site_ancestors)
        where child_sites is a dict mapping all child site ids to the value of site_val_fn(child_site_id)
          and site_ancestors is a dict mapping all site ids to a list of their hasSite children.
        """

        if not self.RR2.has_cached_predicate(PRED.hasSite):
            self.RR2.cache_predicate(PRED.hasSite)

        full_list = [site_id]
        acc = {}

        def _get_ancestors_h(s_id):
            s_child_ids = self.RR2.find_objects(s_id,
                                                PRED.hasSite,
                                                id_only=True)
            if s_child_ids:
                acc[s_id] = s_child_ids
            for scid in s_child_ids:
                _get_ancestors_h(scid)
                full_list.append(scid)

        _get_ancestors_h(site_id)

        return dict([(s, site_val_fn(s)) for s in full_list]), acc

    def get_network_hierarchy(self, device_id, device_val_fn):
        """
        return (child_devices, device_ancestors)
        where child_devices is a dict mapping all child device ids to the value of device_val_fn(child_device_id)
          and device_ancestors is a dict mapping all device ids to a list of their children as per
        """

        if not self.RR2.has_cached_predicate(PRED.hasNetworkParent):
            self.RR2.cache_predicate(PRED.hasNetworkParent)

        full_list = [device_id]
        acc = {}

        def _get_ancestors_h(d_id):
            d_child_ids = self.RR2.find_subjects(d_id,
                                                 PRED.hasNetworkParent,
                                                 id_only=True)
            if d_child_ids:
                acc[d_id] = d_child_ids
            for dcid in d_child_ids:
                _get_ancestors_h(dcid)
                full_list.append(dcid)

        _get_ancestors_h(device_id)

        return dict([(d, device_val_fn(d)) for d in full_list]), acc
class TestEnhancedResourceRegistryClient(PyonTestCase):

    def setUp(self):
        self.rr = Mock()
        self.RR2 = EnhancedResourceRegistryClient(self.rr)
        
    def sample_resource(self):
        return any_old(RT.InstrumentDevice)
        
    def test_init(self):
        pass


    def test_create(self):
        """
        test resource creation in normal case
        """
        # get objects
        good_sample_resource = self.sample_resource()

        #configure Mock
        self.rr.create.return_value = ('111', 'bla')
        self.rr.find_resources.return_value = ([], [])

        sample_resource_id = self.RR2.create(good_sample_resource, RT.InstrumentDevice)

        self.rr.create.assert_called_once_with(good_sample_resource)
        self.assertEqual(sample_resource_id, '111')



    def test_create_bad_wrongtype(self):
        """
        test resource creation failure for wrong type
        """
        # get objects

        bad_sample_resource = any_old(RT.PlatformDevice)
        delattr(bad_sample_resource, "name")

        #configure Mock
        self.rr.create.return_value = ('111', 'bla')
        self.rr.find_resources.return_value = ([], [])

        self.assertRaises(BadRequest, self.RR2.create, bad_sample_resource, RT.InstrumentDevice)


    def test_create_bad_noname(self):
        """
        test resource creation failure for no name
        """
        # get objects

        bad_sample_resource = self.sample_resource()
        delattr(bad_sample_resource, "name")

        #configure Mock
        self.rr.create.return_value = ('111', 'bla')
        self.rr.find_resources.return_value = ([], [])

        self.assertRaises(BadRequest, self.RR2.create, bad_sample_resource, RT.InstrumentDevice)


#    def test_create_bad_dupname(self):
#        """
#        test resource creation failure for duplicate name
#        """
#        # get objects
#
#        bad_sample_resource = self.sample_resource()
#        #really, the resource doesn't matter; it's the retval from find that matters
#
#        #configure Mock
#        self.rr.create.return_value = ('111', 'bla')
#        self.rr.find_resources.return_value = ([0], [0])
#
#        self.assertRaises(BadRequest, self.RR2.create, bad_sample_resource, RT.InstrumentDevice)
#


    def test_read(self):
        """
        test resource read (passthru)
        """
        # get objects
        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret

        response = self.RR2.read("111", RT.InstrumentDevice)
        self.rr.read.assert_called_once_with("111")
        self.assertEqual(response, myret)
        #self.assertDictEqual(response.__dict__,
        #                     self.sample_resource().__dict__)


    def test_read_bad_wrongtype(self):
        """
        test resource read (passthru)
        """
        # get objects
        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret

        self.assertRaises(BadRequest, self.RR2.read, "111", RT.PlatformDevice)
        self.rr.read.assert_called_once_with("111")


    def test_update(self):
        """
        test resource update in normal case
        """
        # get objects

        good_sample_resource = self.sample_resource()
        setattr(good_sample_resource, "_id", "111")

        #configure Mock
        self.rr.update.return_value = ('111', 'bla')
        self.rr.find_resources.return_value = ([], [])

        self.RR2.update(good_sample_resource, RT.InstrumentDevice)

        self.rr.update.assert_called_once_with(good_sample_resource)


    def test_update_bad_wrongtype(self):
        """
        test update failure due to duplicate name
        """
        # get objects

        bad_sample_resource = self.sample_resource()

        self.assertRaises(BadRequest, self.RR2.update, bad_sample_resource, RT.PlatformDevice)

#
#    def test_update_bad_dupname(self):
#        """
#        test update failure due to duplicate name
#        """
#        # get objects
#
#        bad_sample_resource = self.sample_resource()
#        setattr(bad_sample_resource, "_id", "111")
#
#        self.rr.find_resources.return_value = ([0], [0])
#        self.assertRaises(BadRequest, self.RR2.update, bad_sample_resource, RT.InstrumentDevice)


    def test_update_bad_noid(self):
        """
        test update failure due to duplicate name
        """
        # get objects

        bad_sample_resource = self.sample_resource()


        self.rr.find_resources.return_value = ([0], [0])
        self.assertRaises(BadRequest, self.RR2.update, bad_sample_resource, RT.InstrumentDevice)


    def test_retire(self):
        """
        test retire
        """
        # get objects

        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret
        self.rr.delete.return_value = None
        self.rr.retire.return_value = None

        try:
            self.RR2.retire("111", RT.InstrumentDevice)
        except TypeError as te:
            # for logic tests that run into mock trouble
            if "'Mock' object is not iterable" != te.message:
                raise te
            else:
                raise SkipTest("Must test this with INT test")
        except Exception as e:
            raise e

        #self.rr.read.assert_called_with("111", "")
        self.rr.retire.assert_called_once_with("111")


    def test_retire_bad_wrongtype(self):
        """
        test resource read (passthru)
        """
        # get objects
        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret

        self.assertRaises(BadRequest, self.RR2.retire, "111", RT.PlatformDevice)
        self.rr.read.assert_called_once_with("111")


    def test_pluck_delete(self):
        """
        test delete
        """
        # get objects

        myret = self.sample_resource()

        #configure Mock
        self.rr.read.return_value = myret
        self.rr.delete.return_value = None
        self.rr.find_resources.return_value = None
        self.rr.find_objects.return_value = (["2"], ["2"])
        self.rr.find_subjects.return_value = (["3"], ["3"])

        self.RR2.pluck_delete("111", RT.InstrumentDevice)

        self.rr.delete.assert_called_once_with("111")


    def test_advance_lcs(self):
        """
        call RR when the transition ISN'T retire
        """
        self.RR2.advance_lcs("111", LCE.PLAN)
        self.rr.execute_lifecycle_transition.assert_called_once_with(resource_id="111", transition_event=LCE.PLAN)

        self.RR2.advance_lcs("222", LCE.RETIRE)
        self.rr.retire.assert_called_once_with("222")


    def test_delete_association(self):
        self.rr.get_association.return_value = "111"
        self.RR2.delete_association("a", "b", "c")
        self.rr.delete_association.assert_called_once_with("111")


    def test_delete_all_object_associations(self):
        self.rr.find_associations.return_value = ["111"]
        self.RR2.delete_object_associations("x")
        self.rr.delete_association.assert_called_once_with("111")


    def test_delete_all_subject_associations(self):
        self.rr.find_associations.return_value = ["111"]
        self.RR2.delete_subject_associations("x")
        self.rr.delete_association.assert_called_once_with("111")

    def test_pluck(self):
        self.rr.find_subjects.return_value = (["111"], ["aaa"])
        self.rr.find_objects.return_value = (["222"], ["bbb"])
        self.RR2.pluck("x")
        #self.rr.delete_association.assert_called_with("bbb")
        self.rr.delete_association.assert_called_with("aaa")
        self.assertEqual(self.rr.delete_association.call_count, 2)


    def test_find_objects_using_id(self):
        self.tbase_find_objects("x_id")


    def test_find_objects_using_ionobj(self):
        obj = any_old(RT.InstrumentDevice)
        setattr(obj, "_id", "foo_id")
        self.tbase_find_objects(obj)


    def test_find_objects_using_junk(self):
        self.tbase_find_objects(1)


    def tbase_find_objects(self, sample_obj):
        """
        test all 8 flavors of find objects: return IonObjects/ids, return single/multiple, use predicate/no-predicate
        """

        def rst():
            self.rr.find_objects.reset_mock()
            self.rr.find_objects.return_value = ([], [])
            self.assertEqual(0, self.rr.find_subjects.call_count)

        def rst1():
            self.rr.find_objects.reset_mock()
            self.rr.find_objects.return_value = (["x"], ["x"])
            self.assertEqual(0, self.rr.find_subjects.call_count)

        def rst2():
            self.rr.find_objects.reset_mock()
            self.rr.find_objects.return_value = (["x", "y"], ["z", "k"])
            self.assertEqual(0, self.rr.find_subjects.call_count)

        x = sample_obj
        xx = x
        if hasattr(x, "_id"):
            xx = x._id

        # find none
        rst()
        self.RR2.find_instrument_models_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)

        rst()
        self.assertRaises(NotFound, self.RR2.find_instrument_model_of_instrument_device_using_has_model, x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)

        rst()
        self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)

        rst()
        self.assertRaises(NotFound, self.RR2.find_instrument_model_id_of_instrument_device_using_has_model, x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)

        # find one
        rst1()
        self.RR2.find_instrument_models_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)

        rst1()
        self.RR2.find_instrument_model_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)

        rst1()
        self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)

        rst1()
        self.RR2.find_instrument_model_id_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)


        # find multiples
        rst2()
        self.RR2.find_instrument_models_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)

        rst2()
        self.assertRaises(Inconsistent, self.RR2.find_instrument_model_of_instrument_device_using_has_model, x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)

        rst2()
        self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)

        rst2()
        self.assertRaises(Inconsistent, self.RR2.find_instrument_model_id_of_instrument_device_using_has_model, x)
        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)

#        # find using
#        rst2()
#        self.RR2.find_instrument_models_of_instrument_device_using_has_model(x)
#        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)
#
#        rst2()
#        self.assertRaises(Inconsistent, self.RR2.find_instrument_model_of_instrument_device_using_has_model, x)
#        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=False)
#
#        rst2()
#        self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(x)
#        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)
#
#        rst2()
#        self.assertRaises(Inconsistent, self.RR2.find_instrument_model_id_of_instrument_device_using_has_model, x)
#        self.rr.find_objects.assert_called_once_with(subject=xx, predicate=PRED.hasModel, object_type=RT.InstrumentModel, id_only=True)


    def test_find_subjects_using_id(self):
        self.tbase_find_subjects("x_id")


    def test_find_subjects_using_ionobj(self):
        obj = any_old(RT.InstrumentDevice)
        setattr(obj, "_id", "foo_id")
        self.tbase_find_subjects(obj)


    def test_find_subjects_using_junk(self):
        self.tbase_find_subjects(1)


    def tbase_find_subjects(self, sample_obj):
        """
        test all 8 flavors of find subjects: return IonObjects/ids, return single/multiple, use predicate/no-predicate
        """
        def rst():
            self.rr.find_subjects.reset_mock()
            self.rr.find_subjects.return_value = ([], [])
            self.assertEqual(0, self.rr.find_objects.call_count)

        def rst1():
            self.rr.find_subjects.reset_mock()
            self.rr.find_subjects.return_value = (["x"], ["x"])
            self.assertEqual(0, self.rr.find_objects.call_count)

        def rst2():
            self.rr.find_subjects.reset_mock()
            self.rr.find_subjects.return_value = (["x", "y"], ["z", "k"])
            self.assertEqual(0, self.rr.find_objects.call_count)

        x = sample_obj
        xx = x
        if hasattr(x, "_id"):
            xx = x._id

        # find none
        rst()
        self.RR2.find_instrument_devices_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)

        rst()
        self.assertRaises(NotFound, self.RR2.find_instrument_device_by_instrument_model_using_has_model, x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)

        rst()
        self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)

        rst()
        self.assertRaises(NotFound, self.RR2.find_instrument_device_id_by_instrument_model_using_has_model, x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)


        # find 1
        rst1()
        self.RR2.find_instrument_devices_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)

        rst1()
        self.RR2.find_instrument_device_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)

        rst1()
        self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)

        rst1()
        self.RR2.find_instrument_device_id_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)


        # find multiple
        rst2()
        self.RR2.find_instrument_devices_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)

        rst2()
        self.assertRaises(Inconsistent, self.RR2.find_instrument_device_by_instrument_model_using_has_model, x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)

        rst2()
        self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)

        rst2()
        self.assertRaises(Inconsistent, self.RR2.find_instrument_device_id_by_instrument_model_using_has_model, x)
        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)

#
#        # find using
#        rst2()
#        self.RR2.find_instrument_devices_by_instrument_model_using_has_model(x)
#        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)
#
#        rst2()
#        self.assertRaises(Inconsistent, self.RR2.find_instrument_device_by_instrument_model_using_has_model, x)
#        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=False)
#
#        rst2()
#        self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(x)
#        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)
#
#        rst2()
#        self.assertRaises(Inconsistent, self.RR2.find_instrument_device_id_by_instrument_model_using_has_model, x)
#        self.rr.find_subjects.assert_called_once_with(object=xx, predicate=PRED.hasModel, subject_type=RT.InstrumentDevice, id_only=True)


    def test_assign_unassign(self):
        """
        test all flavors of assign and unassign: with/without predicates
        """
        x = "x_id"
        y = "y_id"
        self.RR2.assign_instrument_model_to_instrument_device_with_has_model(y, x)
        self.rr.create_association.assert_called_once_with(x, PRED.hasModel, y)

        self.rr.get_association.return_value = "zzz"
        self.RR2.unassign_instrument_model_from_instrument_device_with_has_model(y, x)
        self.rr.delete_association.assert_called_once_with("zzz")

        self.rr.create_association.reset_mock()
        self.RR2.assign_data_product_to_data_process_with_has_output_product(y, x)
        self.rr.create_association.assert_called_once_with(x, PRED.hasOutputProduct, y)

        self.rr.delete_association.reset_mock()
        self.rr.get_association.reset_mock()
        self.rr.get_association.return_value = "aaa"
        self.RR2.unassign_data_product_from_data_process_with_has_output_product(y, x)
        self.rr.delete_association.assert_called_once_with("aaa")

    def test_assign_single_object(self):
        x = "x_id"
        y = "y_id"

        def rst():
            self.rr.find_objects.reset_mock()
            self.rr.get_association.reset_mock()

        rst()
        self.rr.find_objects.return_value = ([], [])
        self.RR2.assign_one_instrument_model_to_instrument_device_with_has_model(y, x)
        self.rr.create_association.assert_called_once_with(x, PRED.hasModel, y)

        rst()
        self.rr.find_objects.return_value = (["a", "b"], ["c", "d"])
        self.assertRaises(Inconsistent, self.RR2.assign_one_instrument_model_to_instrument_device_with_has_model, y, x)

        rst()
        self.rr.find_objects.return_value = (["a"], ["b"])
        self.rr.get_association.return_value = "yay"
        self.RR2.assign_one_instrument_model_to_instrument_device_with_has_model(y, x)

        rst()
        self.rr.find_objects.return_value = (["a"], ["b"])
        self.rr.get_association.side_effect = NotFound("")
        self.assertRaises(BadRequest, self.RR2.assign_one_instrument_model_to_instrument_device_with_has_model, y, x)

    def test_assign_single_subject(self):
        x = "x_id"
        y = "y_id"

        def rst():
            self.rr.find_subjects.reset_mock()
            self.rr.get_association.reset_mock()


        rst()
        self.rr.find_subjects.return_value = ([], [])
        self.RR2.assign_instrument_device_to_one_instrument_site_with_has_device(y, x)
        self.rr.create_association.assert_called_once_with(x, PRED.hasDevice, y)

        rst()
        self.rr.find_subjects.return_value = (["a", "b"], ["c", "d"])
        self.assertRaises(Inconsistent, self.RR2.assign_instrument_device_to_one_instrument_site_with_has_device, y, x)

        rst()
        self.rr.find_subjects.return_value = (["a"], ["b"])
        self.rr.get_association.return_value = "yay"
        self.RR2.assign_instrument_device_to_one_instrument_site_with_has_device(y, x)

        rst()
        self.rr.find_subjects.return_value = (["a"], ["b"])
        self.rr.get_association.side_effect = NotFound("")
        self.assertRaises(BadRequest, self.RR2.assign_instrument_device_to_one_instrument_site_with_has_device, y, x)



    def test_bad_dynamics(self):
        x = "x_id"
        self.RR2.assign_foo_to_bar(x)
        self.rr.assign_foo_to_bar.assert_called_once_with(x)

        self.assertRaises(BadRequest, getattr, self.RR2, "find_instrument_model_of_instrument_device_using_has_site")
        self.assertRaises(BadRequest, getattr, self.RR2, "find_instrument_model_of_instrument_device_using_has_banana")
        #self.assertRaises(BadRequest, getattr, self.RR2, "find_data_product_of_data_process")

        self.RR2.find_sensor_model_by_data_product(x)
        self.rr.find_sensor_model_by_data_product.assert_called_once_with(x)



    def test_cached_predicate_search(self):
        d = "d_id"
        m = "m_id"
        x = "x_id"

        good_assn = DotDict(s=d, st=RT.InstrumentDevice, p=PRED.hasModel, o=m, ot=RT.InstrumentModel)
        bad_assn  = DotDict(s=d, st=RT.PlatformDevice, p=PRED.hasModel, o=m, ot=RT.PlatformModel)

        self.rr.find_associations.return_value = [good_assn, bad_assn]

        self.RR2.cache_predicate(PRED.hasModel)

        self.assertTrue(self.RR2.has_cached_predicate(PRED.hasModel))
        self.rr.find_associations.assert_called_once_with(predicate=PRED.hasModel, id_only=False)

        # object searches that should return 0, 0, 1 results
        results = self.RR2.find_objects(x, PRED.hasModel, RT.InstrumentModel, True)
        self.assertEqual([], results)
        results = self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(x)
        self.assertEqual([], results)
        results = self.RR2.find_instrument_model_ids_of_instrument_device_using_has_model(d)
        self.assertEqual([m], results)

        self.assertEqual(0, self.rr.find_objects.call_count)

        # subject searches that should return 0, 0, 1 results
        results = self.RR2.find_subjects(RT.InstrumentDevice, PRED.hasModel, x, True)
        self.assertEqual([], results)
        results = self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(x)
        self.assertEqual([], results)
        results = self.RR2.find_instrument_device_ids_by_instrument_model_using_has_model(m)
        self.assertEqual([d], results)

        self.assertEqual(0, self.rr.find_subjects.call_count)
class AgentConfigurationBuilder(object):
    def __init__(self, clients, RR2=None):
        self.clients = clients
        self.RR2 = RR2

        if self.RR2 is None:
            log.warn("Creating new RR2")
            self.RR2 = EnhancedResourceRegistryClient(
                self.clients.resource_registry)

        if not isinstance(self.RR2, EnhancedResourceRegistryClient):
            raise AssertionError(
                "Type of self.RR2 is %s not %s" %
                (type(self.RR2), type(EnhancedResourceRegistryClient)))

        self.agent_instance_obj = None
        self.associated_objects = None
        self.last_id = None
        self.will_launch = False
        self.generated_config = False

    def _predicates_to_cache(self):
        return [
            PRED.hasOutputProduct,
            #PRED.hasStream,
            #PRED.hasStreamDefinition,
            PRED.hasAgentInstance,
            PRED.hasAgentDefinition,
            PRED.hasDataset,
            PRED.hasDevice,
            PRED.hasNetworkParent,
            #PRED.hasParameterContext,
        ]

    def _resources_to_cache(self):
        return [  #RT.StreamDefinition,
            RT.ParameterDictionary,
            #RT.ParameterContext,
        ]

    def _update_cached_predicates(self):
        # cache some predicates for in-memory lookups
        preds = self._predicates_to_cache()
        log.debug("updating cached predicates: %s" % preds)
        time_caching_start = get_ion_ts()
        for pred in preds:
            log.debug(" - %s", pred)
            self.RR2.cache_predicate(pred)
        time_caching_stop = get_ion_ts()

        total_time = int(time_caching_stop) - int(time_caching_start)

        log.info("Cached %s predicates in %s seconds", len(preds),
                 total_time / 1000.0)

    def _update_cached_resources(self):
        # cache some resources for in-memory lookups
        rsrcs = self._resources_to_cache()
        log.debug("updating cached resources: %s" % rsrcs)
        time_caching_start = get_ion_ts()
        for r in rsrcs:
            log.debug(" - %s", r)
            self.RR2.cache_resources(r)
        time_caching_stop = get_ion_ts()

        total_time = int(time_caching_stop) - int(time_caching_start)

        log.info("Cached %s resource types in %s seconds", len(rsrcs),
                 total_time / 1000.0)

    def _clear_caches(self):
        log.warn("Clearing caches")
        for r in self._resources_to_cache():
            self.RR2.clear_cached_resource(r)

        for p in self._predicates_to_cache():
            self.RR2.clear_cached_predicate(p)

    def _lookup_means(self):
        """
        return a dict indicating how various related resources will be looked up

        The dict is keyed on association type:
        PRED.hasAgentInstance -> device type
        PRED.hasModel -> model type
        PRED.hasAgentDefinition -> agent type
        """
        raise NotImplementedError("Extender of class must implement this")

    def _augment_dict(self, title, basedict, newitems):
        # TODO: pyon.util.containers has dict_merge for this purpose (without logs)
        for k, v in newitems.iteritems():
            if k in basedict:
                prev_v = basedict[k]
                # just warn if the new value is different
                if v != prev_v:
                    log.warn("Overwriting %s[%s] of '%s' with '%s'", title, k,
                             prev_v, v)
                else:
                    log.debug(
                        "Overwriting %s[%s] with same value already assigned '%s'",
                        title, k, v)
            basedict[k] = v

    def _check_associations(self):
        assert self.agent_instance_obj
        assert self.associated_objects

        lookup_means = self._lookup_means()
        assert lookup_means

        # make sure we've picked up the associations we expect
        def check_keys(somekeys):
            for k in somekeys:
                assert k in lookup_means
                assert lookup_means[k] in self.associated_objects

        #check_keys([PRED.hasAgentInstance, PRED.hasModel, PRED.hasAgentDefinition])
        check_keys([PRED.hasAgentInstance, PRED.hasAgentDefinition])
        assert RT.ProcessDefinition in self.associated_objects

    def set_agent_instance_object(self, agent_instance_obj):
        """
        Set the agent instance object that we'll be interacting with

        it may be necessary to set this several times, such as if external operations update the object
        """
        assert agent_instance_obj._id

        if self.last_id != agent_instance_obj._id:
            self.associated_objects = None

        self.agent_instance_obj = agent_instance_obj
        self.last_id = agent_instance_obj._id
        self.generated_config = False

    def prepare(self, will_launch=True):
        """
        Prepare (validate) an agent for launch, fetching all associated resources

        @param will_launch - whether the running status should be checked -- set false if just generating config
        """
        assert self.agent_instance_obj

        # fetch caches just in time
        if any([
                not self.RR2.has_cached_predicate(x)
                for x in self._predicates_to_cache()
        ]):
            self._update_cached_predicates()

        if any([
                not self.RR2.has_cached_resource(x)
                for x in self._resources_to_cache()
        ]):
            self._update_cached_resources()

        # validate the associations, then pick things up
        self._collect_agent_instance_associations()

        if will_launch:
            # if there is an agent pid then assume that a drive is already started
            agent_process_id = ResourceAgentClient._get_agent_process_id(
                self._get_device()._id)
            if agent_process_id:
                raise BadRequest(
                    "Agent Instance already running for this device pid: %s" %
                    str(agent_process_id))

        self.will_launch = will_launch
        return self.generate_config()

    def _generate_org_governance_name(self):
        log.debug("_generate_org_governance_name for %s",
                  self.agent_instance_obj.name)
        log.debug(
            "retrieve the Org governance name to which this agent instance belongs"
        )
        try:
            org_obj = self.RR2.find_subject(RT.Org,
                                            PRED.hasResource,
                                            self.agent_instance_obj._id,
                                            id_only=False)
            return org_obj.org_governance_name
        except NotFound:
            return ''
        except:
            raise

    def _generate_device_type(self):
        log.debug("_generate_device_type for %s", self.agent_instance_obj.name)
        return type(self._get_device()).__name__

    def _generate_driver_config(self):
        log.debug("_generate_driver_config for %s",
                  self.agent_instance_obj.name)
        # get default config
        driver_config = self.agent_instance_obj.driver_config

        agent_obj = self._get_agent()

        # Create driver config.
        add_driver_config = {
            'workdir': tempfile.gettempdir(),
            'dvr_mod': agent_obj.driver_module,
            'dvr_cls': agent_obj.driver_class
        }

        self._augment_dict("Agent driver_config", driver_config,
                           add_driver_config)

        return driver_config

    def _get_param_dict_by_name(self, name):
        dict_obj = self.RR2.find_resources_by_name(RT.ParameterDictionary,
                                                   name)[0]
        parameter_contexts = \
            self.RR2.find_parameter_contexts_of_parameter_dictionary_using_has_parameter_context(dict_obj._id)
        return DatasetManagementService.build_parameter_dictionary(
            dict_obj, parameter_contexts)

    def _find_streamdef_for_dp_and_pdict(self, dp_id, pdict_id):
        # Given a pdict_id and a data_product_id find the stream def in the middle
        pdict_stream_defs = self.RR2.find_stream_definition_ids_by_parameter_dictionary_using_has_parameter_dictionary(
            pdict_id)
        stream_def_id = self.RR2.find_stream_definition_id_of_data_product_using_has_stream_definition(
            dp_id)
        result = stream_def_id if stream_def_id in pdict_stream_defs else None
        return result

    def _generate_stream_config(self):
        log.debug("_generate_stream_config for %s",
                  self.agent_instance_obj.name)
        dsm = self.clients.dataset_management
        psm = self.clients.pubsub_management

        agent_obj = self._get_agent()
        device_obj = self._get_device()

        streams_dict = {}
        for stream_cfg in agent_obj.stream_configurations:
            #create a stream def for each param dict to match against the existing data products
            streams_dict[stream_cfg.stream_name] = {
                'param_dict_name': stream_cfg.parameter_dictionary_name
            }

        #retrieve the output products
        # TODO: What about platforms? other things?
        device_id = device_obj._id
        data_product_objs = self.RR2.find_data_products_of_instrument_device_using_has_output_product(
            device_id)

        stream_config = {}
        for d in data_product_objs:
            stream_def_id = self.RR2.find_stream_definition_id_of_data_product_using_has_stream_definition(
                d._id)
            for stream_name, stream_info_dict in streams_dict.items():
                # read objects from cache to be compared
                pdict = self.RR2.find_resource_by_name(
                    RT.ParameterDictionary,
                    stream_info_dict.get('param_dict_name'))
                stream_def_id = self._find_streamdef_for_dp_and_pdict(
                    d._id, pdict._id)

                if stream_def_id:
                    #model_param_dict = self.RR2.find_resources_by_name(RT.ParameterDictionary,
                    #                                         stream_info_dict.get('param_dict_name'))[0]
                    #model_param_dict = self._get_param_dict_by_name(stream_info_dict.get('param_dict_name'))
                    #stream_route = self.RR2.read(product_stream_id).stream_route
                    product_stream_id = self.RR2.find_stream_id_of_data_product_using_has_stream(
                        d._id)
                    stream_def = psm.read_stream_definition(stream_def_id)
                    stream_route = psm.read_stream_route(
                        stream_id=product_stream_id)

                    from pyon.core.object import IonObjectSerializer
                    stream_def_dict = IonObjectSerializer().serialize(
                        stream_def)
                    stream_def_dict.pop('type_')

                    if stream_name in stream_config:
                        log.warn("Overwriting stream_config[%s]", stream_name)

                    stream_config[stream_name] = {
                        'routing_key': stream_route.
                        routing_key,  # TODO: Serialize stream_route together
                        'stream_id': product_stream_id,
                        'stream_definition_ref': stream_def_id,
                        'stream_def_dict': stream_def_dict,
                        'exchange_point': stream_route.exchange_point,
                        # TODO: This is redundant and very large - the param dict is in the stream_def_dict ???
                        'parameter_dictionary':
                        stream_def.parameter_dictionary,
                    }

        log.debug("Stream config generated")
        log.trace("generate_stream_config: %s", stream_config)
        return stream_config

    def _generate_agent_config(self):
        log.debug("_generate_agent_config for %s",
                  self.agent_instance_obj.name)
        # should override this
        return {}

    def _generate_alerts_config(self):
        log.debug("_generate_alerts_config for %s",
                  self.agent_instance_obj.name)
        # should override this
        return self.agent_instance_obj.alerts

    def _generate_startup_config(self):
        log.debug("_generate_startup_config for %s",
                  self.agent_instance_obj.name)
        # should override this
        return {}

    def _generate_children(self):
        log.debug("_generate_children for %s", self.agent_instance_obj.name)
        # should override this
        return {}

    def _generate_skeleton_config_block(self):
        log.info("Generating skeleton config block for %s",
                 self.agent_instance_obj.name)

        # merge the agent config into the default config
        agent_config = dict_merge(self._get_agent().agent_default_config,
                                  self.agent_instance_obj.agent_config, True)

        # Create agent_config.
        agent_config['instance_name'] = self.agent_instance_obj.name
        agent_config[
            'org_governance_name'] = self._generate_org_governance_name()
        agent_config['device_type'] = self._generate_device_type()
        agent_config['driver_config'] = self._generate_driver_config()
        agent_config['stream_config'] = self._generate_stream_config()
        agent_config['agent'] = self._generate_agent_config()
        agent_config['aparam_alerts_config'] = self._generate_alerts_config()
        agent_config['startup_config'] = self._generate_startup_config()
        agent_config['children'] = self._generate_children()

        log.info("DONE generating skeleton config block for %s",
                 self.agent_instance_obj.name)

        return agent_config

    def _summarize_children(self, config_dict):
        ret = dict([(v['instance_name'], self._summarize_children(v))
                    for k, v in config_dict["children"].iteritems()])
        #agent_config['agent']['resource_id']
        return ret

    def generate_config(self):
        """
        create the generic parts of the configuration including resource_id, egg_uri, and org
        """
        if self.generated_config:
            log.warn(
                "Generating config again for the same Instance object (%s)",
                self.agent_instance_obj.name)

        self._check_associations()

        agent_config = self._generate_skeleton_config_block()

        device_obj = self._get_device()
        agent_obj = self._get_agent()

        log.debug("complement agent_config with resource_id")
        if 'agent' not in agent_config:
            agent_config['agent'] = {'resource_id': device_obj._id}
        elif 'resource_id' not in agent_config.get('agent'):
            agent_config['agent']['resource_id'] = device_obj._id

        log.debug("add egg URI if available")
        if agent_obj.driver_uri:
            agent_config['driver_config']['process_type'] = (
                DriverProcessType.EGG, )
            agent_config['driver_config']['dvr_egg'] = agent_obj.driver_uri
        else:
            agent_config['driver_config']['process_type'] = (
                DriverProcessType.PYTHON_MODULE, )

        if log.isEnabledFor(logging.INFO):
            tree = self._summarize_children(agent_config)
            log.info("Children of %s are %s", self.agent_instance_obj.name,
                     tree)

        self.generated_config = True

        return agent_config

    def record_launch_parameters(self, agent_config):
        """
        record process id of the launch
        """
        log.debug(
            "add the process id, the generated config, and update the resource"
        )
        self.agent_instance_obj.agent_config = agent_config
        self.agent_instance_obj.agent_spawn_config = agent_config
        self.RR2.update(self.agent_instance_obj)

        log.debug('completed agent start')

    def _collect_agent_instance_associations(self):
        """
        Collect related resources to this agent instance

        Returns a dict of objects necessary to start this instance, keyed on the values of self._lookup_means()
            PRED.hasAgentInstance   -> device_obj
            PRED.hasModel           -> model_obj
            PRED.hasAgentDefinition -> agent_obj
            RT.ProcessDefinition    -> process_def_obj

        """
        assert self.agent_instance_obj

        lookup_means = self._lookup_means()

        assert lookup_means
        assert PRED.hasAgentInstance in lookup_means
        assert PRED.hasModel in lookup_means
        assert PRED.hasAgentDefinition in lookup_means
        #assert PRED.hasProcessDefinition in lookup_means

        lu = lookup_means

        ret = {}

        log.debug("retrieve the associated device")
        device_obj = self.RR2.find_subject(
            subject_type=lu[PRED.hasAgentInstance],
            predicate=PRED.hasAgentInstance,
            object=self.agent_instance_obj._id)

        ret[lu[PRED.hasAgentInstance]] = device_obj
        device_id = device_obj._id

        log.debug("%s '%s' connected to %s '%s' (L4-CI-SA-RQ-363)",
                  lu[PRED.hasAgentInstance], str(device_id),
                  type(self.agent_instance_obj).__name__,
                  str(self.agent_instance_obj._id))

        #        log.debug("retrieve the model associated with the device")
        #        model_obj = self.RR2.find_object(subject=device_id,
        #                                         predicate=PRED.hasModel,
        #                                         object_type=lu[PRED.hasModel])
        #
        #        ret[lu[PRED.hasModel]] = model_obj
        #        model_id = model_obj

        #retrive the stream info for this model
        #todo: add stream info to the platform model create
        #        streams_dict = platform_models_objs[0].custom_attributes['streams']
        #        if not streams_dict:
        #            raise BadRequest("Device model does not contain stream configuation used in launching the agent. Model: '%s", str(platform_models_objs[0]) )
        #TODO: get the agent from the instance not from the model!!!!!!!
        log.debug("retrieve the agent associated with the model")
        agent_obj = self.RR2.find_object(
            subject=self.agent_instance_obj._id,
            predicate=PRED.hasAgentDefinition,
            object_type=lu[PRED.hasAgentDefinition])

        ret[lu[PRED.hasAgentDefinition]] = agent_obj
        agent_id = agent_obj._id

        if not agent_obj.stream_configurations:
            raise BadRequest(
                "Agent '%s' does not contain stream configuration used in launching"
                % str(agent_obj))

        log.debug("retrieve the process definition associated with this agent")
        process_def_obj = self.RR2.find_object(
            subject=agent_id,
            predicate=PRED.hasProcessDefinition,
            object_type=RT.ProcessDefinition)

        ret[RT.ProcessDefinition] = process_def_obj

        #retrieve the output products
        data_product_objs = self.RR2.find_objects(device_id,
                                                  PRED.hasOutputProduct,
                                                  RT.DataProduct,
                                                  id_only=False)

        if not data_product_objs:
            raise NotFound("No output Data Products attached to this Device " +
                           str(device_id))

        #retrieve the streams assoc with each defined output product
        for data_product_obj in data_product_objs:
            product_id = data_product_obj._id
            try:
                self.RR2.find_stream_id_of_data_product_using_has_stream(
                    product_id)  # check one stream per product
            except NotFound:
                errmsg = "Device '%s' (%s) has data products %s.  Data product '%s' (%s) has no stream ID." % \
                    (device_obj.name,
                     device_obj._id,
                     [dp._id for dp in data_product_objs],
                     data_product_obj.name,
                     product_id)
                raise NotFound(errmsg)

            # some products may not be persisted
            try:
                # check one dataset per product
                self.RR2.find_dataset_id_of_data_product_using_has_dataset(
                    product_id)
            except NotFound:
                log.warn(
                    "Data product '%s' of device %s ('%s') does not appear to be persisted -- no dataset",
                    product_id, device_obj.name, device_obj._id)

        self.associated_objects = ret

    def _get_device(self):
        self._check_associations()
        return self.associated_objects[self._lookup_means()[
            PRED.hasAgentInstance]]


#    def _get_model(self):
#        self._check_associations()
#        return self.associated_objects[self._lookup_means()[PRED.hasModel]]

    def _get_agent(self):
        self._check_associations()
        return self.associated_objects[self._lookup_means()[
            PRED.hasAgentDefinition]]

    def _get_process_definition(self):
        self._check_associations()
        return self.associated_objects[RT.ProcessDefinition]
class RollXBuilder(object):
    """
    for rollups and rolldowns
    """

    def __init__(self, process=None):
        """
        the process should be the "self" of a service instance
        """
        assert process
        self.process = process
        self.RR2 = EnhancedResourceRegistryClient(self.process.clients.resource_registry)

    def get_toplevel_platformsite(self, site_id):
        if not self.RR2.has_cached_predicate(PRED.hasSite):
            self.RR2.cache_predicate(PRED.hasSite)

        parent_ids = self.RR2.find_platform_site_ids_by_platform_site_using_has_site(site_id)

        if 0 == len(parent_ids):
            return site_id  # assume this to be the top level

        return self.get_toplevel_platformsite(parent_ids[0])

    def get_parent_network_nodes(self, site_id):
        """
        return the parent nodes of this network node, including the given node
        """
        if not self.RR2.has_cached_predicate(PRED.hasNetworkParent):
            self.RR2.cache_predicate(PRED.hasNetworkParent)

        def get_h(acc, some_id):
            acc.append(some_id)
            parent_ids = self.RR2.find_platform_device_ids_of_platform_device_using_has_network_parent(site_id)
            if 0 == len(parent_ids):
                return acc
            return get_h(acc, parent_ids[0])

        return get_h([], site_id)

    def get_toplevel_network_node(self, device_id):
        if not self.RR2.has_cached_predicate(PRED.hasNetworkParent):
            self.RR2.cache_predicate(PRED.hasNetworkParent)

        parent_ids = self.RR2.find_platform_device_ids_of_platform_device_using_has_network_parent(device_id)

        if 0 == len(parent_ids):
            # it can only be the network parent if it has this association
            if 0 < len(self.RR2.find_platform_device_ids_by_platform_device_using_has_network_parent(device_id)):
                return device_id  # assume this to be top level
            else:
                return None

        return self.get_toplevel_network_node(parent_ids[0])

    def get_site_hierarchy(self, site_id, site_val_fn):
        """
        return (child_sites, site_ancestors)
        where child_sites is a dict mapping all child site ids to the value of site_val_fn(child_site_id)
          and site_ancestors is a dict mapping all site ids to a list of their hasSite children.
        """

        if not self.RR2.has_cached_predicate(PRED.hasSite):
            self.RR2.cache_predicate(PRED.hasSite)

        full_list = [site_id]
        acc = {}

        def _get_ancestors_h(s_id):
            s_child_ids = self.RR2.find_objects(s_id, PRED.hasSite, id_only=True)
            if s_child_ids:
                acc[s_id] = s_child_ids
            for scid in s_child_ids:
                _get_ancestors_h(scid)
                full_list.append(scid)

        _get_ancestors_h(site_id)

        return dict([(s, site_val_fn(s)) for s in full_list]), acc

    def get_network_hierarchy(self, device_id, device_val_fn):
        """
        return (child_devices, device_ancestors)
        where child_devices is a dict mapping all child device ids to the value of device_val_fn(child_device_id)
          and device_ancestors is a dict mapping all device ids to a list of their children as per
        """

        if not self.RR2.has_cached_predicate(PRED.hasNetworkParent):
            self.RR2.cache_predicate(PRED.hasNetworkParent)

        full_list = [device_id]
        acc = {}

        def _get_ancestors_h(d_id):
            d_child_ids = self.RR2.find_subjects(d_id, PRED.hasNetworkParent, id_only=True)
            if d_child_ids:
                acc[d_id] = d_child_ids
            for dcid in d_child_ids:
                _get_ancestors_h(dcid)
                full_list.append(dcid)

        _get_ancestors_h(device_id)

        return dict([(d, device_val_fn(d)) for d in full_list]), acc
    def _get_site_extension(self, site_id='', ext_associations=None, ext_exclude=None, user_id=''):
        """Returns an InstrumentDeviceExtension object containing additional related information

        @param site_id    str
        @param ext_associations    dict
        @param ext_exclude    list
        @retval observatory    ObservatoryExtension
        @throws BadRequest    A parameter is missing
        @throws NotFound    An object with the specified observatory_id does not exist
        """

        if not site_id:
            raise BadRequest("The site_id parameter is empty")

        extended_resource_handler = ExtendedResourceContainer(self)

        extended_site = extended_resource_handler.create_extended_resource_container(
            extended_resource_type=OT.SiteExtension,
            resource_id=site_id,
            computed_resource_type=OT.SiteComputedAttributes,
            ext_associations=ext_associations,
            ext_exclude=ext_exclude,
            user_id=user_id)

        RR2 = EnhancedResourceRegistryClient(self.RR)
        RR2.cache_predicate(PRED.hasModel)

        # Get status of Site instruments.
        a, b =  self._get_instrument_states(extended_site.instrument_devices)
        extended_site.instruments_operational, extended_site.instruments_not_operational = a, b

        # lookup all hasModel predicates
        # lookup is a 2d associative array of [subject type][subject id] -> object id
        lookup = dict([(rt, {}) for rt in [RT.InstrumentDevice, RT.PlatformDevice]])
        for a in RR2.filter_cached_associations(PRED.hasModel, lambda assn: assn.st in lookup):
            lookup[a.st][a.s] = a.o

        def retrieve_model_objs(rsrc_list, object_type):
        # rsrc_list is devices that need models looked up.  object_type is the resource type (a device)
        # not all devices have models (represented as None), which kills read_mult.  so, extract the models ids,
        #  look up all the model ids, then create the proper output
            model_list = [lookup[object_type].get(r._id) for r in rsrc_list]
            model_uniq = list(set([m for m in model_list if m is not None]))
            model_objs = self.RR2.read_mult(model_uniq)
            model_dict = dict(zip(model_uniq, model_objs))
            return [model_dict.get(m) for m in model_list]

        extended_site.instrument_models = retrieve_model_objs(extended_site.instrument_devices, RT.InstrumentDevice)
        extended_site.platform_models   = retrieve_model_objs(extended_site.platform_devices, RT.PlatformDevice)


        # Status computation
        extended_site.computed.instrument_status = [AgentStatusBuilder.get_aggregate_status_of_device(idev._id, "aggstatus")
                                                    for idev in extended_site.instrument_devices]
        extended_site.computed.platform_status   = [AgentStatusBuilder.get_aggregate_status_of_device(pdev._id, "aggstatus")
                                                    for pdev in extended_site.platform_devices]

#            AgentStatusBuilder.add_device_aggregate_status_to_resource_extension(device_id,
#                                                                                    'aggstatus',
#                                                                                    extended_site)
        def status_unknown():
            return ComputedIntValue(status=ComputedValueAvailability.PROVIDED, value=StatusType.STATUS_UNKNOWN)
        extended_site.computed.communications_status_roll_up = status_unknown()
        extended_site.computed.power_status_roll_up          = status_unknown()
        extended_site.computed.data_status_roll_up           = status_unknown()
        extended_site.computed.location_status_roll_up       = status_unknown()
        extended_site.computed.aggregated_status             = status_unknown()

        extended_site.computed.site_status = [StatusType.STATUS_UNKNOWN] * len(extended_site.sites)



        return extended_site, RR2
示例#10
0
        def freeze():

            if isinstance(resource_registry_client,
                          EnhancedResourceRegistryClient):
                RR2 = resource_registry_client
            else:
                RR2 = EnhancedResourceRegistryClient(resource_registry_client)

            for p in predicate_list:
                if not RR2.has_cached_predicate(p):
                    RR2.cache_predicate(p)

            def get_related_resources_partial_fn(predicate_dictionary,
                                                 resource_whitelist):
                """
                This function generates a resource crawler from 2 data structures representing desired crawl behavior

                The predicate dictionary is keyed on a predicate type, whose value is a 2-tuple of booleans
                  the first boolean is whether to crawl subject-object, the second boolean for object-subject
                  For example: dict([(PRED.hasModel, (False, True)]) would generate a crawler that could find
                               platforms or instruments with a given model

                The resource whitelist is a list of resource types that will be crawled.

                The return value of this function is a function that accepts a resource id and returns a list
                  of associations related (based on crawl behavior)
                """
                log.trace(
                    "get_related_resources_partial_fn predicate_dict=%s rsrc_whitelist=%s",
                    predicate_dictionary, resource_whitelist)

                # assertions on data types
                assert type({}) == type(predicate_dictionary)
                for v in predicate_dictionary.values():
                    assert type((True, True)) == type(v)
                assert type([]) == type(resource_whitelist)

                for rt in resource_whitelist:
                    RR2.cache_resources(rt)

                def lookup_fn(resource_id):
                    """
                    return a dict of related resources as dictated by the pred dict and whitelist
                     - the key is the next resource id to crawl
                     - the value is the entire association
                    """
                    retval = {}

                    sto_match = lambda assn: assn.s == resource_id and assn.ot in resource_whitelist
                    ots_match = lambda assn: assn.o == resource_id and assn.st in resource_whitelist
                    for p, (search_sto,
                            search_ots) in predicate_dictionary.iteritems():
                        if search_sto:
                            for a in RR2.filter_cached_associations(
                                    p, sto_match):
                                log.trace("lookup_fn matched %s object", a.ot)
                                retval[a.o] = a
                        if search_ots:
                            for a in RR2.filter_cached_associations(
                                    p, ots_match):
                                log.trace("lookup_fn matched %s subject", a.st)
                                retval[a.s] = a

                    return retval

                def get_related_resources_h(accum, input_resource_id,
                                            recursion_limit):
                    """
                    This is a recursive helper function that does the work of crawling for related resources

                    The accum is a tuple: (set of associations that are deemed "Related", set of "seen" resources)

                    The input resource id is the current resource being crawled

                    The recursion limit decrements with each recursive call, ending at 0.  So -1 for infinity.

                    The return value is a list of associations
                    """
                    if 0 == recursion_limit:
                        return accum

                    if -1000 > recursion_limit:
                        log.warn(
                            "Terminating related resource recursion, hit -1000"
                        )
                        return accum

                    acc, seen = accum

                    matches = lookup_fn(input_resource_id)
                    log.trace("get_related_resources_h got matches %s", [
                        dict((k, "%s %s %s" % (a.st, a.p, a.ot))
                             for k, a in matches.iteritems())
                    ])

                    unseen = set(matches.keys()) - seen
                    seen.add(input_resource_id)
                    acc = acc | set(matches.values())

                    #if log.isEnabledFor(logging.TRACE):
                    #    summary = {}
                    #    for a in acc:
                    #        label = "%s %s %s" % (a.st, a.p, a.ot)
                    #        if not label in summary: summary[label] = 0
                    #        summary[label] += 1
                    #    log.trace("acc2 is now %s", ["%s x%d" % (k, v) for k, v in summary.iteritems()])

                    def looper(acc2, input_rsrc_id):
                        return get_related_resources_h(acc2, input_rsrc_id,
                                                       recursion_limit - 1)

                    h_ret = reduce(looper, unseen, (acc, seen))
                    #h_ret = reduce(looper, unseen, (acc, seen))
                    #(h_ret_acc, h_ret_seen) = h_ret
                    #log.trace("h_ret is %s", ["%s %s %s" % (a.st, a.p, a.ot) for a in h_ret_acc])
                    return h_ret

                def get_related_resources_fn(input_resource_id,
                                             recursion_limit=1024):
                    """
                    This is the function that finds related resources.

                    input_resource_id and recursion_limit are self explanatory

                    The return value is a list of associations.
                    """
                    retval, _ = get_related_resources_h(
                        (set([]), set([])), input_resource_id, recursion_limit)
                    log.trace("final_ret is %s",
                              ["%s %s %s" % (a.st, a.p, a.ot) for a in retval])
                    return list(retval)

                return get_related_resources_fn  # retval of get_related_resources_partial_fn

            return get_related_resources_partial_fn  # retval of freeze()