def __init__(
            self,
            yaml_filename='ion/agents/platform/rsn/simulator/network.yml'):
        self._ndef = NetworkUtil.deserialize_network_definition(
            file(yaml_filename))
        self._platform_types = self._ndef.platform_types
        self._pnodes = self._ndef.pnodes

        # registered event listeners: {url: [(event_type, reg_time), ...], ...},
        # where reg_time is the NTP time of (latest) registration.
        # NOTE: for simplicity, we don't keep info about unregistered listeners
        self._reg_event_listeners = {}

        self._event_notifier = EventNotifier()
        # EventGenerator only kept while there are listeners registered
        self._event_generator = None
    def __init__(
            self,
            yaml_filename='ion/agents/platform/rsn/simulator/network.yml',
            events_filename='ion/agents/platform/rsn/simulator/events.yml'):
        self._ndef = NetworkUtil.deserialize_network_definition(
            file(yaml_filename))
        self._pnodes = self._ndef.pnodes

        # note that all ports are implicitly init'ed with state='OFF'
        self._portState = {}

        # registered event listeners: {url: reg_time, ...},
        # where reg_time is the NTP time of (latest) registration.
        # NOTE: for simplicity, we don't keep info about unregistered listeners
        self._reg_event_listeners = {}

        self._event_notifier = EventNotifier()
        # EventGenerator only kept while there are listeners registered
        self._event_generator = None
        self._events_filename = events_filename
    def __init__(self, yaml_filename='ion/agents/platform/rsn/simulator/network.yml'):
        self._ndef = NetworkUtil.deserialize_network_definition(file(yaml_filename))
        self._platform_types = self._ndef.platform_types
        self._pnodes = self._ndef.pnodes

        # registered event listeners: {url: reg_time, ...},
        # where reg_time is the NTP time of (latest) registration.
        # NOTE: for simplicity, we don't keep info about unregistered listeners
        self._reg_event_listeners = {}

        self._event_notifier = EventNotifier()
        # EventGenerator only kept while there are listeners registered
        self._event_generator = None
class CIOMSSimulator(CIOMSClient):
    """
    Implementation of CIOMSClient for testing purposes.
    It adds some methods intended to be used by tests (they are prefixed with
    "x_" and are "public" to make them visible through the xml/rpc mechanism).
    """

    # _raise_exception: see disable() and enable()
    _raise_exception = False

    @classmethod
    def x_disable(cls):
        """
        Makes any subsequent call to any public API operation to raise an
        exception. This allows to test for the "lost connection" case.
        """
        cls._raise_exception = True

    @classmethod
    def x_enable(cls):
        """
        Cancels the effect of disable() (so the simulator continues to
        operate normally).
        """
        cls._raise_exception = False

    def __init__(self, yaml_filename='ion/agents/platform/rsn/simulator/network.yml'):
        self._ndef = NetworkUtil.deserialize_network_definition(file(yaml_filename))
        self._platform_types = self._ndef.platform_types
        self._pnodes = self._ndef.pnodes

        # registered event listeners: {url: reg_time, ...},
        # where reg_time is the NTP time of (latest) registration.
        # NOTE: for simplicity, we don't keep info about unregistered listeners
        self._reg_event_listeners = {}

        self._event_notifier = EventNotifier()
        # EventGenerator only kept while there are listeners registered
        self._event_generator = None

    def _start_event_generator_if_listeners(self):
        if not self._event_generator and len(self._reg_event_listeners):
            self._event_generator = EventGenerator(self._event_notifier)
            self._event_generator.start()
            log.debug("event generator started (%s listeners registered)",
                      len(self._reg_event_listeners))

    def _stop_event_generator_if_no_listeners(self):
        if self._event_generator and not len(self._reg_event_listeners):
            log.debug("event generator stopping (no listeners registered)")
            self._event_generator.stop()
            self._event_generator = None

    def _deactivate_simulator(self):
        """
        Special method only intended to be called for when the simulator is run
        in "embedded" form. See test_oms_simulator for the particular case.
        """
        log.info("_deactivate_simulator called. event_generator=%s; %s listeners registered",
                 self._event_generator, len(self._reg_event_listeners))
        if self._event_generator:
            self._event_generator.stop()
            self._event_generator = None

    def _enter(self):
        """
        Called when entering any of the CI-OMS interface methods methods.
        """
        self._dispatch_synthetic_exception()

    def _dispatch_synthetic_exception(self):
        """
        Called by all CI_OMS interface methods to dispatch the
        simulation of connection lost.
        """
        if self._raise_exception:
            msg = "(LC) synthetic exception from CIOMSSimulator"
            log.debug(msg)
            raise Exception(msg)

    def ping(self):
        self._enter()

        return "pong"

    def get_platform_map(self):
        self._enter()

        return self._ndef.get_map()

    def get_platform_types(self):
        self._enter()

        return self._platform_types

    def get_platform_metadata(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]

        # TODO capture/include appropriate elements
        md = {}
        if pnode.name:
            md['name'] = pnode.name
        if pnode.parent:
            md['parent_platform_id'] = pnode.parent.platform_id
        md['platform_types'] = pnode.platform_types

        return {platform_id: md}

    def get_platform_attributes(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        attrs = self._pnodes[platform_id].attrs
        ret_infos = {}
        for attrName in attrs:
            attr = attrs[attrName]
            ret_infos[attrName] = attr.defn

        return {platform_id: ret_infos}

    def get_platform_attribute_values(self, platform_id, req_attrs):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        # complete time window until current time:
        to_time = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for attrName, from_time in req_attrs:
            if attrName in attrs:
                attr = attrs[attrName]
                values = generate_values(platform_id, attr.attr_id, from_time, to_time)
                vals[attrName] = values
                # Note: values == [] if there are no values.
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_ID

        return {platform_id: vals}

    def set_platform_attribute_values(self, platform_id, input_attrs):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        assert isinstance(input_attrs, list)

        timestamp = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for (attrName, attrValue) in input_attrs:
            if attrName in attrs:
                attr = attrs[attrName]
                if attr.writable:
                    #
                    # TODO check given attrValue
                    #
                    vals[attrName] = (attrValue, timestamp)
                else:
                    vals[attrName] = InvalidResponse.ATTRIBUTE_NOT_WRITABLE
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_ID

        retval = {platform_id: vals}
        log.debug("set_platform_attribute_values returning: %s", str(retval))
        return retval

    def get_platform_ports(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        ports = {}
        for port_id, port in self._pnodes[platform_id].ports.iteritems():
            ports[port_id] = {'network': port.network,
                              'state'  : port.state}

        return {platform_id: ports}

    def connect_instrument(self, platform_id, port_id, instrument_id, attributes):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        result = None
        if instrument_id in port.instruments:
            result = InvalidResponse.INSTRUMENT_ALREADY_CONNECTED
        elif port.state == "ON":
            # TODO: confirm that port must be OFF so instrument can be connected
            result = InvalidResponse.PORT_IS_ON

        if result is None:
            # verify required attributes are provided:
            for key in REQUIRED_INSTRUMENT_ATTRIBUTES:
                if not key in attributes:
                    result = InvalidResponse.MISSING_INSTRUMENT_ATTRIBUTE
                    log.warn("connect_instrument called with missing attribute: %s"% key)
                    break

        if result is None:
            # verify given attributes are recognized:
            for key in attributes.iterkeys():
                if not key in REQUIRED_INSTRUMENT_ATTRIBUTES:
                    result = InvalidResponse.INVALID_INSTRUMENT_ATTRIBUTE
                    log.warn("connect_instrument called with invalid attribute: %s"% key)
                    break

        if result is None:
            # NOTE: values simply accepted without any validation
            connected_instrument = InstrumentNode(instrument_id)
            port.add_instrument(connected_instrument)
            attrs = connected_instrument.attrs
            result = {}
            for key, val in attributes.iteritems():
                attrs[key] = val  # set the value of the attribute:
                result[key] = val # in the result, indicate that the value was set

        return {platform_id: {port_id: {instrument_id: result}}}

    def disconnect_instrument(self, platform_id, port_id, instrument_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        if instrument_id not in port.instruments:
            result = InvalidResponse.INSTRUMENT_NOT_CONNECTED
        elif port.state == "ON":
            # TODO: confirm that port must be OFF so instrument can be disconnected
            result = InvalidResponse.PORT_IS_ON
        else:
            port.remove_instrument(instrument_id)
            result = NormalResponse.INSTRUMENT_DISCONNECTED

        return {platform_id: {port_id: {instrument_id: result}}}

    def get_connected_instruments(self, platform_id, port_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        result = {}
        for instrument_id in port.instruments:
            result[instrument_id] = port.instruments[instrument_id].attrs

        return {platform_id: {port_id: result}}

    def turn_on_platform_port(self, platform_id, port_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if port.state == "ON":
            result = NormalResponse.PORT_ALREADY_ON
            log.warn("port %s in platform %s already turned on." % (port_id, platform_id))
        else:
            port.set_state("ON")
            result = NormalResponse.PORT_TURNED_ON
            log.info("port %s in platform %s turned on." % (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def turn_off_platform_port(self, platform_id, port_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if port.state == "OFF":
            result = NormalResponse.PORT_ALREADY_OFF
            log.warn("port %s in platform %s already turned off." % (port_id, platform_id))
        else:
            port.set_state("OFF")
            result = NormalResponse.PORT_TURNED_OFF
            log.info("port %s in platform %s turned off." % (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def _validate_event_listener_url(self, url):
        """
        Does a basic, static validation of the url.
        """
        # TODO implement it; for now always returning True
        return True

    def register_event_listener(self, url):
        self._enter()

        # NOTE: event_types was previously a parameter to this operation. To
        # minimize changes in the code, I introduced an 'ALL' event type to
        # be used here explicitly.
        event_type = 'ALL'

        log.debug("register_event_listener called: url=%r", url)

        if not self._validate_event_listener_url(url):
            return {url: InvalidResponse.EVENT_LISTENER_URL}

        if not url in self._reg_event_listeners:
            # create entry for this new url
            reg_time = self._event_notifier.add_listener(url, event_type)
            self._reg_event_listeners[url] = reg_time
            log.info("registered url=%r", url)
        else:
            # already registered:
            reg_time = self._reg_event_listeners[url]

        self._start_event_generator_if_listeners()

        return {url: reg_time}

    def unregister_event_listener(self, url):
        self._enter()

        # NOTE: event_types was previously a parameter to this operation. To
        # minimize changes in the code, I introduced an 'ALL' event type to
        # be used here explicitly.
        event_type = 'ALL'

        log.debug("unregister_event_listener called: url=%r", url)

        if not url in self._reg_event_listeners:
            return {url: InvalidResponse.EVENT_LISTENER_URL}

        #
        # registered, so remove it
        #
        unreg_time = self._event_notifier.remove_listener(url, event_type)
        del self._reg_event_listeners[url]

        log.info("unregistered url=%r", url)

        self._stop_event_generator_if_no_listeners()

        return {url: unreg_time}

    def get_registered_event_listeners(self):
        self._enter()

        return self._reg_event_listeners

    def get_checksum(self, platform_id):
        """
        @note the checksum is always computed, which is fine for the simulator.
        A more realistic and presumably more efficient implementation would
        exploit some caching mechanism along with appropriate invalidation
        upon modifications to the platform information.
        """
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]
        checksum = pnode.compute_checksum()

        return {platform_id: checksum}
class CIOMSSimulator(CIOMSClient):
    """
    Implementation of CIOMSClient for testing purposes.
    It adds some methods intended to be used by tests (they are prefixed with
    "x_" and are "public" to make them visible through the xml/rpc mechanism).
    """

    # _raise_exception: see disable() and enable()
    _raise_exception = False

    @classmethod
    def x_disable(cls):
        """
        Makes any subsequent call to any public API operation to raise an
        exception. This allows to test for the "lost connection" case.
        """
        cls._raise_exception = True

    @classmethod
    def x_enable(cls):
        """
        Cancels the effect of disable() (so the simulator continues to
        operate normally).
        """
        cls._raise_exception = False

    def __init__(
            self,
            yaml_filename='ion/agents/platform/rsn/simulator/network.yml',
            events_filename='ion/agents/platform/rsn/simulator/events.yml'):
        self._ndef = NetworkUtil.deserialize_network_definition(
            file(yaml_filename))
        self._pnodes = self._ndef.pnodes

        # note that all ports are implicitly init'ed with state='OFF'
        self._portState = {}

        # registered event listeners: {url: reg_time, ...},
        # where reg_time is the NTP time of (latest) registration.
        # NOTE: for simplicity, we don't keep info about unregistered listeners
        self._reg_event_listeners = {}

        self._event_notifier = EventNotifier()
        # EventGenerator only kept while there are listeners registered
        self._event_generator = None
        self._events_filename = events_filename

    def _start_event_generator_if_listeners(self):
        if not self._event_generator and len(self._reg_event_listeners):
            self._event_generator = EventGenerator(self._event_notifier,
                                                   self._events_filename)
            self._event_generator.start()
            log.debug("event generator started (%s listeners registered)",
                      len(self._reg_event_listeners))

    def _stop_event_generator_if_no_listeners(self):
        if self._event_generator and not len(self._reg_event_listeners):
            log.debug("event generator stopping (no listeners registered)")
            self._event_generator.stop()
            self._event_generator = None

    def _deactivate_simulator(self):
        """
        Special method only intended to be called for when the simulator is run
        in "embedded" form. See test_oms_simulator for the particular case.
        """
        log.info(
            "_deactivate_simulator called. event_generator=%s; %s listeners registered",
            self._event_generator, len(self._reg_event_listeners))
        if self._event_generator:
            self._event_generator.stop()
            self._event_generator = None

    def _enter(self):
        """
        Called when entering any of the CI-OMS interface methods.
        """
        self._dispatch_synthetic_exception()

    def _dispatch_synthetic_exception(self):
        """
        Called by all CI_OMS interface methods to dispatch the
        simulation of connection lost.
        """
        if self._raise_exception:
            msg = "(LC) synthetic exception from CIOMSSimulator"
            log.debug(msg)
            raise Exception(msg)

    def ping(self):
        self._enter()

        return "pong"

    def get_platform_metadata(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]

        # TODO capture/include appropriate elements
        md = {}
        if pnode.name:
            md['name'] = pnode.name
        if pnode.parent:
            md['parent_platform_id'] = pnode.parent.platform_id

        return {platform_id: md}

    def get_platform_attribute_values(self, platform_id, req_attrs):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        # complete time window until current time:
        to_time = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for attrName, from_time in req_attrs:
            if attrName in attrs:
                attr = attrs[attrName]
                values = generate_values(platform_id, attr.attr_id, from_time,
                                         to_time)
                vals[attrName] = values
                # Note: values == [] if there are no values.
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_ID

        return {platform_id: vals}

    def _set_port_state(self, platform_id, port_id, state):
        pp_id = '%s %s' % (platform_id, port_id)
        self._portState[pp_id] = state

    def _get_port_state(self, platform_id, port_id):
        pp_id = '%s %s' % (platform_id, port_id)
        return self._portState.get(pp_id, 'OFF')

    def get_platform_ports(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        ports = {}
        for port_id in self._pnodes[platform_id].ports:
            state = self._get_port_state(platform_id, port_id)
            ports[port_id] = {'state': state}

        return {platform_id: ports}

    def turn_on_platform_port(self, platform_id, port_id, src):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        state = self._get_port_state(platform_id, port_id)
        if state == "ON":
            result = NormalResponse.PORT_ALREADY_ON
            log.debug("port %s in platform %s already turned on." %
                      (port_id, platform_id))
        else:
            self._set_port_state(platform_id, port_id, 'ON')
            result = NormalResponse.PORT_TURNED_ON
            log.info("port %s in platform %s turned on." %
                     (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def turn_off_platform_port(self, platform_id, port_id, src):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        state = self._get_port_state(platform_id, port_id)
        if state == "OFF":
            result = NormalResponse.PORT_ALREADY_OFF
            log.debug("port %s in platform %s already turned off." %
                      (port_id, platform_id))
        else:
            self._set_port_state(platform_id, port_id, 'OFF')
            result = NormalResponse.PORT_TURNED_OFF
            log.info("port %s in platform %s turned off." %
                     (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def set_over_current(self, platform_id, port_id, ma, us, src):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        # OK, but we don't do anything else here, just accept.
        result = NormalResponse.PORT_SET_OVER_CURRENT

        return {platform_id: {port_id: result}}

    def _validate_event_listener_url(self, url):
        """
        Does a basic, static validation of the url.
        """
        # TODO implement it; for now always returning True
        return True

    def register_event_listener(self, url):
        self._enter()

        # NOTE: event_types was previously a parameter to this operation. To
        # minimize changes in the code, I introduced an 'ALL' event type to
        # be used here explicitly.
        event_type = 'ALL'

        log.debug("register_event_listener called: url=%r", url)

        if not self._validate_event_listener_url(url):
            return {url: InvalidResponse.EVENT_LISTENER_URL}

        if not url in self._reg_event_listeners:
            # create entry for this new url
            reg_time = self._event_notifier.add_listener(url, event_type)
            self._reg_event_listeners[url] = reg_time
            log.info("registered url=%r", url)
        else:
            # already registered:
            reg_time = self._reg_event_listeners[url]

        self._start_event_generator_if_listeners()

        return {url: reg_time}

    def unregister_event_listener(self, url):
        self._enter()

        # NOTE: event_types was previously a parameter to this operation. To
        # minimize changes in the code, I introduced an 'ALL' event type to
        # be used here explicitly.
        event_type = 'ALL'

        log.debug("unregister_event_listener called: url=%r", url)

        if not url in self._reg_event_listeners:
            return {url: 0}

        #
        # registered, so remove it
        #
        unreg_time = self._event_notifier.remove_listener(url, event_type)
        del self._reg_event_listeners[url]

        log.info("unregistered url=%r", url)

        self._stop_event_generator_if_no_listeners()

        return {url: unreg_time}

    def get_registered_event_listeners(self):
        self._enter()

        return self._reg_event_listeners

    def generate_test_event(self, event):
        self._enter()

        if self._event_generator:  # there are listeners registered.
            # copy event and include the additional fields:
            event_instance = event.copy()
            event_instance['test_event'] = True
            timestamp = ntplib.system_to_ntp_time(time.time())
            if 'timestamp' not in event_instance:
                event_instance['timestamp'] = timestamp
            if 'first_time_timestamp' not in event_instance:
                event_instance['first_time_timestamp'] = timestamp
            # simply notify listeners right away
            self._event_notifier.notify(event_instance)
            return True

        else:  # there are *no* listeners registered.
            return False
class CIOMSSimulator(CIOMSClient):
    """
    Implementation of CIOMSClient for testing purposes.
    """

    def __init__(self, yaml_filename='ion/agents/platform/rsn/simulator/network.yml'):
        self._ndef = NetworkUtil.deserialize_network_definition(file(yaml_filename))
        self._platform_types = self._ndef.platform_types
        self._pnodes = self._ndef.pnodes

        # registered event listeners: {url: [(event_type, reg_time), ...], ...},
        # where reg_time is the NTP time of (latest) registration.
        # NOTE: for simplicity, we don't keep info about unregistered listeners
        self._reg_event_listeners = {}

        self._event_notifier = EventNotifier()
        # EventGenerator only kept while there are listeners registered
        self._event_generator = None

    def _start_event_generator_if_listeners(self):
        if not self._event_generator and len(self._reg_event_listeners):
            self._event_generator = EventGenerator(self._event_notifier)
            self._event_generator.start()
            log.debug("event generator started (%s listeners registered)",
                      len(self._reg_event_listeners))

    def _stop_event_generator_if_no_listeners(self):
        if self._event_generator and not len(self._reg_event_listeners):
            log.debug("event generator stopping (no listeners registered)")
            self._event_generator.stop()
            self._event_generator = None

    def _deactivate_simulator(self):
        """
        Special method only intended to be called for when the simulator is run
        in "embedded" form. See test_oms_simulator for the particular case.
        """
        log.info("_deactivate_simulator called. event_generator=%s; %s listeners registered",
                 self._event_generator, len(self._reg_event_listeners))
        if self._event_generator:
            self._event_generator.stop()
            self._event_generator = None

    def ping(self):
        return "pong"

    def get_platform_map(self):
        return self._ndef.get_map()

    def get_platform_types(self):
        return self._platform_types

    def get_platform_metadata(self, platform_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]

        # TODO capture/include appropriate elements
        md = {}
        if pnode.name:
            md['name'] = pnode.name
        if pnode.parent:
            md['parent_platform_id'] = pnode.parent.platform_id
        md['platform_types'] = pnode.platform_types

        return {platform_id: md}

    def get_platform_attributes(self, platform_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        attrs = self._pnodes[platform_id].attrs
        ret_infos = {}
        for attrName in attrs:
            attr = attrs[attrName]
            ret_infos[attrName] = attr.defn

        return {platform_id: ret_infos}

    def get_platform_attribute_values(self, platform_id, req_attrs):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        # complete time window until current time:
        to_time = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for attrName, from_time in req_attrs:
            if attrName in attrs:
                attr = attrs[attrName]
                values = generate_values(platform_id, attr.attr_id, from_time, to_time)
                vals[attrName] = values
                # Note: values == [] if there are no values.
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_NAME

        return {platform_id: vals}

    def set_platform_attribute_values(self, platform_id, input_attrs):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        assert isinstance(input_attrs, list)

        timestamp = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for (attrName, attrValue) in input_attrs:
            if attrName in attrs:
                attr = attrs[attrName]
                if attr.writable:
                    #
                    # TODO check given attrValue
                    #
                    vals[attrName] = (attrValue, timestamp)
                else:
                    vals[attrName] = InvalidResponse.ATTRIBUTE_NOT_WRITABLE
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_NAME

        retval = {platform_id: vals}
        log.debug("set_platform_attribute_values returning: %s", str(retval))
        return retval

    def get_platform_ports(self, platform_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        ports = {}
        for port_id, port in self._pnodes[platform_id].ports.iteritems():
            ports[port_id] = {'network': port.network}

        return {platform_id: ports}

    def connect_instrument(self, platform_id, port_id, instrument_id, attributes):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        result = None
        if instrument_id in port.instruments:
            result = InvalidResponse.INSTRUMENT_ALREADY_CONNECTED
        elif port.is_on:
            # TODO: confirm that port must be OFF so instrument can be connected
            result = InvalidResponse.PORT_IS_ON

        if result is None:
            # verify required attributes are provided:
            for key in REQUIRED_INSTRUMENT_ATTRIBUTES:
                if not key in attributes:
                    result = InvalidResponse.MISSING_INSTRUMENT_ATTRIBUTE
                    log.warn("connect_instrument called with missing attribute: %s"% key)
                    break

        if result is None:
            # verify given attributes are recognized:
            for key in attributes.iterkeys():
                if not key in REQUIRED_INSTRUMENT_ATTRIBUTES:
                    result = InvalidResponse.INVALID_INSTRUMENT_ATTRIBUTE
                    log.warn("connect_instrument called with invalid attribute: %s"% key)
                    break

        if result is None:
            # NOTE: values simply accepted without any validation
            connected_instrument = InstrumentNode(instrument_id)
            port.add_instrument(connected_instrument)
            attrs = connected_instrument.attrs
            result = {}
            for key, val in attributes.iteritems():
                attrs[key] = val  # set the value of the attribute:
                result[key] = val # in the result, indicate that the value was set

        return {platform_id: {port_id: {instrument_id: result}}}

    def disconnect_instrument(self, platform_id, port_id, instrument_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        if instrument_id not in port.instruments:
            result = InvalidResponse.INSTRUMENT_NOT_CONNECTED
        elif port.is_on:
            # TODO: confirm that port must be OFF so instrument can be disconnected
            result = InvalidResponse.PORT_IS_ON
        else:
            port.remove_instrument(instrument_id)
            result = NormalResponse.INSTRUMENT_DISCONNECTED

        return {platform_id: {port_id: {instrument_id: result}}}

    def get_connected_instruments(self, platform_id, port_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        result = {}
        for instrument_id in port.instruments:
            result[instrument_id] = port.instruments[instrument_id].attrs

        return {platform_id: {port_id: result}}

    def turn_on_platform_port(self, platform_id, port_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if port.is_on:
            result = NormalResponse.PORT_ALREADY_ON
            log.warn("port %s in platform %s already turned on." % (port_id, platform_id))
        else:
            port.set_on(True)
            result = NormalResponse.PORT_TURNED_ON
            log.info("port %s in platform %s turned on." % (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def turn_off_platform_port(self, platform_id, port_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports :
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if not port.is_on:
            result = NormalResponse.PORT_ALREADY_OFF
            log.warn("port %s in platform %s already turned off." % (port_id, platform_id))
        else:
            port.set_on(False)
            result = NormalResponse.PORT_TURNED_OFF
            log.info("port %s in platform %s turned off." % (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def describe_event_types(self, event_type_ids):
        if len(event_type_ids) == 0:
            return EventInfo.EVENT_TYPES

        result = {}
        for k in event_type_ids:
            if not k in EventInfo.EVENT_TYPES:
                result[k] = InvalidResponse.EVENT_TYPE
            else:
                result[k] = EventInfo.EVENT_TYPES[k]

        return result

    def get_events_by_platform_type(self, platform_types):
        if len(platform_types) == 0:
            platform_types = self._platform_types.keys()

        result = {}
        for platform_type in platform_types:
            if not platform_type in self._platform_types:
                result[platform_type] = InvalidResponse.PLATFORM_TYPE
                continue

            result[platform_type] = [v for v in EventInfo.EVENT_TYPES.itervalues() \
                if v['platform_type'] == platform_type]

        return result

    def _validate_event_listener_url(self, url):
        """
        Does a basic, static validation of the url.
        """
        # TODO implement it; for now always returning True
        return True

    def register_event_listener(self, url, event_types):
        log.debug("register_event_listener called: url=%r, event_types=%s",
                 url, str(event_types))

        if not self._validate_event_listener_url(url):
            return {url: InvalidResponse.EVENT_LISTENER_URL}

        if not url in self._reg_event_listeners:
            # create entry for this new url
            existing_pairs = self._reg_event_listeners[url] = []
        else:
            existing_pairs = self._reg_event_listeners[url]

        if len(existing_pairs):
            existing_types, reg_times = zip(*existing_pairs)
        else:
            existing_types = reg_times = []

        if len(event_types) == 0:
            event_types = list(EventInfo.EVENT_TYPES.keys())

        result_list = []
        for event_type in event_types:
            if not event_type in EventInfo.EVENT_TYPES:
                result_list.append((event_type, InvalidResponse.EVENT_TYPE))
                continue

            if event_type in existing_types:
                # already registered:
                reg_time = reg_times[existing_types.index(event_type)]
                result_list.append((event_type, reg_time))
            else:
                #
                # new registration
                #
                reg_time = self._event_notifier.add_listener(url, event_type)
                existing_pairs.append((event_type, reg_time))
                result_list.append((event_type, reg_time))

                log.info("%r registered for event_type=%r", url, event_type)

        self._start_event_generator_if_listeners()

        return {url: result_list}

    def unregister_event_listener(self, url, event_types):
        log.debug("unregister_event_listener called: url=%r, event_types=%s",
                 url, str(event_types))

        if not url in self._reg_event_listeners:
            return {url: InvalidResponse.EVENT_LISTENER_URL}

        existing_pairs = self._reg_event_listeners[url]

        assert len(existing_pairs), "we don't keep any url with empty list"

        existing_types, reg_times = zip(*existing_pairs)

        if len(event_types) == 0:
            event_types = list(EventInfo.EVENT_TYPES.keys())

        result_list = []
        for event_type in event_types:
            if not event_type in EventInfo.EVENT_TYPES:
                result_list.append((event_type, InvalidResponse.EVENT_TYPE))
                continue

            if event_type in existing_types:
                #
                # registered, so remove it
                #
                unreg_time = self._event_notifier.remove_listener(url, event_type)
                idx = existing_types.index(event_type)
                del existing_pairs[idx]
                result_list.append((event_type, unreg_time))

                # update for next iteration (index for next proper removal):
                if len(existing_pairs):
                    existing_types, reg_times = zip(*existing_pairs)
                else:
                    existing_types = reg_times = []

                log.info("%r unregistered for event_type=%r", url, event_type)

            else:
                # not registered, report 0
                unreg_time = 0
                result_list.append((event_type, unreg_time))

        if len(existing_pairs):
            # reflect the updates:
            self._reg_event_listeners[url] = existing_pairs
        else:
            # we don't keep any url with empty list
            del self._reg_event_listeners[url]

        self._stop_event_generator_if_no_listeners()

        return {url: result_list}

    def get_registered_event_listeners(self):
        return self._reg_event_listeners

    def get_checksum(self, platform_id):
        """
        @note the checksum is always computed, which is fine for the simulator.
        A more realistic and presumably more efficient implementation would
        exploit some caching mechanism along with appropriate invalidation
        upon modifications to the platform information.
        """
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]
        checksum = pnode.compute_checksum()

        return {platform_id: checksum}
class CIOMSSimulator(CIOMSClient):
    """
    Implementation of CIOMSClient for testing purposes.
    """
    def __init__(
            self,
            yaml_filename='ion/agents/platform/rsn/simulator/network.yml'):
        self._ndef = NetworkUtil.deserialize_network_definition(
            file(yaml_filename))
        self._platform_types = self._ndef.platform_types
        self._pnodes = self._ndef.pnodes

        # registered event listeners: {url: [(event_type, reg_time), ...], ...},
        # where reg_time is the NTP time of (latest) registration.
        # NOTE: for simplicity, we don't keep info about unregistered listeners
        self._reg_event_listeners = {}

        self._event_notifier = EventNotifier()
        # EventGenerator only kept while there are listeners registered
        self._event_generator = None

    def _start_event_generator_if_listeners(self):
        if not self._event_generator and len(self._reg_event_listeners):
            self._event_generator = EventGenerator(self._event_notifier)
            self._event_generator.start()
            log.debug("event generator started (%s listeners registered)",
                      len(self._reg_event_listeners))

    def _stop_event_generator_if_no_listeners(self):
        if self._event_generator and not len(self._reg_event_listeners):
            log.debug("event generator stopping (no listeners registered)")
            self._event_generator.stop()
            self._event_generator = None

    def _deactivate_simulator(self):
        """
        Special method only intended to be called for when the simulator is run
        in "embedded" form. See test_oms_simulator for the particular case.
        """
        log.info(
            "_deactivate_simulator called. event_generator=%s; %s listeners registered",
            self._event_generator, len(self._reg_event_listeners))
        if self._event_generator:
            self._event_generator.stop()
            self._event_generator = None

    def ping(self):
        return "pong"

    def get_platform_map(self):
        return self._ndef.get_map()

    def get_platform_types(self):
        return self._platform_types

    def get_platform_metadata(self, platform_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]

        # TODO capture/include appropriate elements
        md = {}
        if pnode.name:
            md['name'] = pnode.name
        if pnode.parent:
            md['parent_platform_id'] = pnode.parent.platform_id
        md['platform_types'] = pnode.platform_types

        return {platform_id: md}

    def get_platform_attributes(self, platform_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        attrs = self._pnodes[platform_id].attrs
        ret_infos = {}
        for attrName in attrs:
            attr = attrs[attrName]
            ret_infos[attrName] = attr.defn

        return {platform_id: ret_infos}

    def get_platform_attribute_values(self, platform_id, attrNames, from_time):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        # complete time window until current time:
        to_time = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for attrName in attrNames:
            if attrName in attrs:
                attr = attrs[attrName]
                values = generate_values(platform_id, attr.attr_id, from_time,
                                         to_time)
                vals[attrName] = values
                # Note: values == [] if there are no values.
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_NAME

        return {platform_id: vals}

    def set_platform_attribute_values(self, platform_id, input_attrs):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        assert isinstance(input_attrs, list)

        timestamp = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for (attrName, attrValue) in input_attrs:
            if attrName in attrs:
                attr = attrs[attrName]
                if attr.writable:
                    #
                    # TODO check given attrValue
                    #
                    vals[attrName] = (attrValue, timestamp)
                else:
                    vals[attrName] = InvalidResponse.ATTRIBUTE_NOT_WRITABLE
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_NAME

        retval = {platform_id: vals}
        log.debug("set_platform_attribute_values returning: %s", str(retval))
        return retval

    def get_platform_ports(self, platform_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        ports = {}
        for port_id, port in self._pnodes[platform_id].ports.iteritems():
            ports[port_id] = {'network': port.network}

        return {platform_id: ports}

    def connect_instrument(self, platform_id, port_id, instrument_id,
                           attributes):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        result = None
        if instrument_id in port.instruments:
            result = InvalidResponse.INSTRUMENT_ALREADY_CONNECTED
        elif port.is_on:
            # TODO: confirm that port must be OFF so instrument can be connected
            result = InvalidResponse.PORT_IS_ON

        if result is None:
            # verify required attributes are provided:
            for key in REQUIRED_INSTRUMENT_ATTRIBUTES:
                if not key in attributes:
                    result = InvalidResponse.MISSING_INSTRUMENT_ATTRIBUTE
                    log.warn(
                        "connect_instrument called with missing attribute: %s"
                        % key)
                    break

        if result is None:
            # verify given attributes are recognized:
            for key in attributes.iterkeys():
                if not key in REQUIRED_INSTRUMENT_ATTRIBUTES:
                    result = InvalidResponse.INVALID_INSTRUMENT_ATTRIBUTE
                    log.warn(
                        "connect_instrument called with invalid attribute: %s"
                        % key)
                    break

        if result is None:
            # NOTE: values simply accepted without any validation
            connected_instrument = InstrumentNode(instrument_id)
            port.add_instrument(connected_instrument)
            attrs = connected_instrument.attrs
            result = {}
            for key, val in attributes.iteritems():
                attrs[key] = val  # set the value of the attribute:
                result[
                    key] = val  # in the result, indicate that the value was set

        return {platform_id: {port_id: {instrument_id: result}}}

    def disconnect_instrument(self, platform_id, port_id, instrument_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        if instrument_id not in port.instruments:
            result = InvalidResponse.INSTRUMENT_NOT_CONNECTED
        elif port.is_on:
            # TODO: confirm that port must be OFF so instrument can be disconnected
            result = InvalidResponse.PORT_IS_ON
        else:
            port.remove_instrument(instrument_id)
            result = NormalResponse.INSTRUMENT_DISCONNECTED

        return {platform_id: {port_id: {instrument_id: result}}}

    def get_connected_instruments(self, platform_id, port_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        result = {}
        for instrument_id in port.instruments:
            result[instrument_id] = port.instruments[instrument_id].attrs

        return {platform_id: {port_id: result}}

    def turn_on_platform_port(self, platform_id, port_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if port.is_on:
            result = NormalResponse.PORT_ALREADY_ON
            log.warn("port %s in platform %s already turned on." %
                     (port_id, platform_id))
        else:
            port.set_on(True)
            result = NormalResponse.PORT_TURNED_ON
            log.info("port %s in platform %s turned on." %
                     (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def turn_off_platform_port(self, platform_id, port_id):
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if not port.is_on:
            result = NormalResponse.PORT_ALREADY_OFF
            log.warn("port %s in platform %s already turned off." %
                     (port_id, platform_id))
        else:
            port.set_on(False)
            result = NormalResponse.PORT_TURNED_OFF
            log.info("port %s in platform %s turned off." %
                     (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def describe_event_types(self, event_type_ids):
        if len(event_type_ids) == 0:
            return EventInfo.EVENT_TYPES

        result = {}
        for k in event_type_ids:
            if not k in EventInfo.EVENT_TYPES:
                result[k] = InvalidResponse.EVENT_TYPE
            else:
                result[k] = EventInfo.EVENT_TYPES[k]

        return result

    def get_events_by_platform_type(self, platform_types):
        if len(platform_types) == 0:
            platform_types = self._platform_types.keys()

        result = {}
        for platform_type in platform_types:
            if not platform_type in self._platform_types:
                result[platform_type] = InvalidResponse.PLATFORM_TYPE
                continue

            result[platform_type] = [v for v in EventInfo.EVENT_TYPES.itervalues() \
                if v['platform_type'] == platform_type]

        return result

    def _validate_event_listener_url(self, url):
        """
        Does a basic, static validation of the url.
        """
        # TODO implement it; for now always returning True
        return True

    def register_event_listener(self, url, event_types):
        log.debug("register_event_listener called: url=%r, event_types=%s",
                  url, str(event_types))

        if not self._validate_event_listener_url(url):
            return {url: InvalidResponse.EVENT_LISTENER_URL}

        if not url in self._reg_event_listeners:
            # create entry for this new url
            existing_pairs = self._reg_event_listeners[url] = []
        else:
            existing_pairs = self._reg_event_listeners[url]

        if len(existing_pairs):
            existing_types, reg_times = zip(*existing_pairs)
        else:
            existing_types = reg_times = []

        if len(event_types) == 0:
            event_types = list(EventInfo.EVENT_TYPES.keys())

        result_list = []
        for event_type in event_types:
            if not event_type in EventInfo.EVENT_TYPES:
                result_list.append((event_type, InvalidResponse.EVENT_TYPE))
                continue

            if event_type in existing_types:
                # already registered:
                reg_time = reg_times[existing_types.index(event_type)]
                result_list.append((event_type, reg_time))
            else:
                #
                # new registration
                #
                reg_time = self._event_notifier.add_listener(url, event_type)
                existing_pairs.append((event_type, reg_time))
                result_list.append((event_type, reg_time))

                log.info("%r registered for event_type=%r", url, event_type)

        self._start_event_generator_if_listeners()

        return {url: result_list}

    def unregister_event_listener(self, url, event_types):
        log.debug("unregister_event_listener called: url=%r, event_types=%s",
                  url, str(event_types))

        if not url in self._reg_event_listeners:
            return {url: InvalidResponse.EVENT_LISTENER_URL}

        existing_pairs = self._reg_event_listeners[url]

        assert len(existing_pairs), "we don't keep any url with empty list"

        existing_types, reg_times = zip(*existing_pairs)

        if len(event_types) == 0:
            event_types = list(EventInfo.EVENT_TYPES.keys())

        result_list = []
        for event_type in event_types:
            if not event_type in EventInfo.EVENT_TYPES:
                result_list.append((event_type, InvalidResponse.EVENT_TYPE))
                continue

            if event_type in existing_types:
                #
                # registered, so remove it
                #
                unreg_time = self._event_notifier.remove_listener(
                    url, event_type)
                idx = existing_types.index(event_type)
                del existing_pairs[idx]
                result_list.append((event_type, unreg_time))

                # update for next iteration (index for next proper removal):
                if len(existing_pairs):
                    existing_types, reg_times = zip(*existing_pairs)
                else:
                    existing_types = reg_times = []

                log.info("%r unregistered for event_type=%r", url, event_type)

            else:
                # not registered, report 0
                unreg_time = 0
                result_list.append((event_type, unreg_time))

        if len(existing_pairs):
            # reflect the updates:
            self._reg_event_listeners[url] = existing_pairs
        else:
            # we don't keep any url with empty list
            del self._reg_event_listeners[url]

        self._stop_event_generator_if_no_listeners()

        return {url: result_list}

    def get_registered_event_listeners(self):
        return self._reg_event_listeners

    def get_checksum(self, platform_id):
        """
        @note the checksum is always computed, which is fine for the simulator.
        A more realistic and presumably more efficient implementation would
        exploit some caching mechanism along with appropriate invalidation
        upon modifications to the platform information.
        """
        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]
        checksum = pnode.compute_checksum()

        return {platform_id: checksum}
Exemple #8
0
class CIOMSSimulator(CIOMSClient):
    """
    Implementation of CIOMSClient for testing purposes.
    It adds some methods intended to be used by tests (they are prefixed with
    "x_" and are "public" to make them visible through the xml/rpc mechanism).
    """

    # _raise_exception: see disable() and enable()
    _raise_exception = False

    @classmethod
    def x_disable(cls):
        """
        Makes any subsequent call to any public API operation to raise an
        exception. This allows to test for the "lost connection" case.
        """
        cls._raise_exception = True

    @classmethod
    def x_enable(cls):
        """
        Cancels the effect of disable() (so the simulator continues to
        operate normally).
        """
        cls._raise_exception = False

    def __init__(
            self,
            yaml_filename='ion/agents/platform/rsn/simulator/network.yml'):
        self._ndef = NetworkUtil.deserialize_network_definition(
            file(yaml_filename))
        self._platform_types = self._ndef.platform_types
        self._pnodes = self._ndef.pnodes

        # registered event listeners: {url: reg_time, ...},
        # where reg_time is the NTP time of (latest) registration.
        # NOTE: for simplicity, we don't keep info about unregistered listeners
        self._reg_event_listeners = {}

        self._event_notifier = EventNotifier()
        # EventGenerator only kept while there are listeners registered
        self._event_generator = None

    def _start_event_generator_if_listeners(self):
        if not self._event_generator and len(self._reg_event_listeners):
            self._event_generator = EventGenerator(self._event_notifier)
            self._event_generator.start()
            log.debug("event generator started (%s listeners registered)",
                      len(self._reg_event_listeners))

    def _stop_event_generator_if_no_listeners(self):
        if self._event_generator and not len(self._reg_event_listeners):
            log.debug("event generator stopping (no listeners registered)")
            self._event_generator.stop()
            self._event_generator = None

    def _deactivate_simulator(self):
        """
        Special method only intended to be called for when the simulator is run
        in "embedded" form. See test_oms_simulator for the particular case.
        """
        log.info(
            "_deactivate_simulator called. event_generator=%s; %s listeners registered",
            self._event_generator, len(self._reg_event_listeners))
        if self._event_generator:
            self._event_generator.stop()
            self._event_generator = None

    def _enter(self):
        """
        Called when entering any of the CI-OMS interface methods.
        """
        self._dispatch_synthetic_exception()

    def _dispatch_synthetic_exception(self):
        """
        Called by all CI_OMS interface methods to dispatch the
        simulation of connection lost.
        """
        if self._raise_exception:
            msg = "(LC) synthetic exception from CIOMSSimulator"
            log.debug(msg)
            raise Exception(msg)

    def ping(self):
        self._enter()

        return "pong"

    def get_platform_map(self):
        self._enter()

        return self._ndef.get_map()

    def get_platform_types(self):
        self._enter()

        return self._platform_types

    def get_platform_metadata(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]

        # TODO capture/include appropriate elements
        md = {}
        if pnode.name:
            md['name'] = pnode.name
        if pnode.parent:
            md['parent_platform_id'] = pnode.parent.platform_id
        md['platform_types'] = pnode.platform_types

        return {platform_id: md}

    def get_platform_attributes(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        attrs = self._pnodes[platform_id].attrs
        ret_infos = {}
        for attrName in attrs:
            attr = attrs[attrName]
            ret_infos[attrName] = attr.defn

        return {platform_id: ret_infos}

    def get_platform_attribute_values(self, platform_id, req_attrs):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        # complete time window until current time:
        to_time = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for attrName, from_time in req_attrs:
            if attrName in attrs:
                attr = attrs[attrName]
                values = generate_values(platform_id, attr.attr_id, from_time,
                                         to_time)
                vals[attrName] = values
                # Note: values == [] if there are no values.
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_ID

        return {platform_id: vals}

    def set_platform_attribute_values(self, platform_id, input_attrs):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        assert isinstance(input_attrs, list)

        timestamp = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for (attrName, attrValue) in input_attrs:
            if attrName in attrs:
                attr = attrs[attrName]
                if attr.writable:
                    #
                    # TODO check given attrValue
                    #
                    vals[attrName] = (attrValue, timestamp)
                else:
                    vals[attrName] = InvalidResponse.ATTRIBUTE_NOT_WRITABLE
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_ID

        retval = {platform_id: vals}
        log.debug("set_platform_attribute_values returning: %s", str(retval))
        return retval

    def get_platform_ports(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        ports = {}
        for port_id, port in self._pnodes[platform_id].ports.iteritems():
            ports[port_id] = {'network': port.network, 'state': port.state}

        return {platform_id: ports}

    def connect_instrument(self, platform_id, port_id, instrument_id,
                           attributes):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        result = None
        if instrument_id in port.instruments:
            result = InvalidResponse.INSTRUMENT_ALREADY_CONNECTED
        elif port.state == "ON":
            # TODO: confirm that port must be OFF so instrument can be connected
            result = InvalidResponse.PORT_IS_ON

        if result is None:
            # verify required attributes are provided:
            for key in REQUIRED_INSTRUMENT_ATTRIBUTES:
                if not key in attributes:
                    result = InvalidResponse.MISSING_INSTRUMENT_ATTRIBUTE
                    log.warn(
                        "connect_instrument called with missing attribute: %s"
                        % key)
                    break

        if result is None:
            # verify given attributes are recognized:
            for key in attributes.iterkeys():
                if not key in REQUIRED_INSTRUMENT_ATTRIBUTES:
                    result = InvalidResponse.INVALID_INSTRUMENT_ATTRIBUTE
                    log.warn(
                        "connect_instrument called with invalid attribute: %s"
                        % key)
                    break

        if result is None:
            # NOTE: values simply accepted without any validation
            connected_instrument = InstrumentNode(instrument_id)
            port.add_instrument(connected_instrument)
            attrs = connected_instrument.attrs
            result = {}
            for key, val in attributes.iteritems():
                attrs[key] = val  # set the value of the attribute:
                result[
                    key] = val  # in the result, indicate that the value was set

        return {platform_id: {port_id: {instrument_id: result}}}

    def disconnect_instrument(self, platform_id, port_id, instrument_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        if instrument_id not in port.instruments:
            result = InvalidResponse.INSTRUMENT_NOT_CONNECTED
        elif port.state == "ON":
            # TODO: confirm that port must be OFF so instrument can be disconnected
            result = InvalidResponse.PORT_IS_ON
        else:
            port.remove_instrument(instrument_id)
            result = NormalResponse.INSTRUMENT_DISCONNECTED

        return {platform_id: {port_id: {instrument_id: result}}}

    def get_connected_instruments(self, platform_id, port_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)

        result = {}
        for instrument_id in port.instruments:
            result[instrument_id] = port.instruments[instrument_id].attrs

        return {platform_id: {port_id: result}}

    def turn_on_platform_port(self, platform_id, port_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if port.state == "ON":
            result = NormalResponse.PORT_ALREADY_ON
            log.warn("port %s in platform %s already turned on." %
                     (port_id, platform_id))
        else:
            port.set_state("ON")
            result = NormalResponse.PORT_TURNED_ON
            log.info("port %s in platform %s turned on." %
                     (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def turn_off_platform_port(self, platform_id, port_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if port.state == "OFF":
            result = NormalResponse.PORT_ALREADY_OFF
            log.warn("port %s in platform %s already turned off." %
                     (port_id, platform_id))
        else:
            port.set_state("OFF")
            result = NormalResponse.PORT_TURNED_OFF
            log.info("port %s in platform %s turned off." %
                     (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def _validate_event_listener_url(self, url):
        """
        Does a basic, static validation of the url.
        """
        # TODO implement it; for now always returning True
        return True

    def register_event_listener(self, url):
        self._enter()

        # NOTE: event_types was previously a parameter to this operation. To
        # minimize changes in the code, I introduced an 'ALL' event type to
        # be used here explicitly.
        event_type = 'ALL'

        log.debug("register_event_listener called: url=%r", url)

        if not self._validate_event_listener_url(url):
            return {url: InvalidResponse.EVENT_LISTENER_URL}

        if not url in self._reg_event_listeners:
            # create entry for this new url
            reg_time = self._event_notifier.add_listener(url, event_type)
            self._reg_event_listeners[url] = reg_time
            log.info("registered url=%r", url)
        else:
            # already registered:
            reg_time = self._reg_event_listeners[url]

        self._start_event_generator_if_listeners()

        return {url: reg_time}

    def unregister_event_listener(self, url):
        self._enter()

        # NOTE: event_types was previously a parameter to this operation. To
        # minimize changes in the code, I introduced an 'ALL' event type to
        # be used here explicitly.
        event_type = 'ALL'

        log.debug("unregister_event_listener called: url=%r", url)

        if not url in self._reg_event_listeners:
            return {url: 0}

        #
        # registered, so remove it
        #
        unreg_time = self._event_notifier.remove_listener(url, event_type)
        del self._reg_event_listeners[url]

        log.info("unregistered url=%r", url)

        self._stop_event_generator_if_no_listeners()

        return {url: unreg_time}

    def get_registered_event_listeners(self):
        self._enter()

        return self._reg_event_listeners

    def generate_test_event(self, event):
        self._enter()

        if self._event_generator:  # there are listeners registered.
            # copy event and include the additional fields:
            event_instance = event.copy()
            event_instance['test_event'] = True
            timestamp = ntplib.system_to_ntp_time(time.time())
            if 'timestamp' not in event_instance:
                event_instance['timestamp'] = timestamp
            if 'first_time_timestamp' not in event_instance:
                event_instance['first_time_timestamp'] = timestamp
            # simply notify listeners right away
            self._event_notifier.notify(event_instance)
            return True

        else:  # there are *no* listeners registered.
            return False

    def get_checksum(self, platform_id):
        """
        @note the checksum is always computed, which is fine for the simulator.
        A more realistic and presumably more efficient implementation would
        exploit some caching mechanism along with appropriate invalidation
        upon modifications to the platform information.
        """
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]
        checksum = pnode.compute_checksum()

        return {platform_id: checksum}
Exemple #9
0
class CIOMSSimulator(CIOMSClient):
    """
    Implementation of CIOMSClient for testing purposes.
    It adds some methods intended to be used by tests (they are prefixed with
    "x_" and are "public" to make them visible through the xml/rpc mechanism).
    """

    # _raise_exception: see disable() and enable()
    _raise_exception = False

    @classmethod
    def x_disable(cls):
        """
        Makes any subsequent call to any public API operation to raise an
        exception. This allows to test for the "lost connection" case.
        """
        cls._raise_exception = True

    @classmethod
    def x_enable(cls):
        """
        Cancels the effect of disable() (so the simulator continues to
        operate normally).
        """
        cls._raise_exception = False

    def __init__(
        self,
        yaml_filename="ion/agents/platform/rsn/simulator/network.yml",
        events_filename="ion/agents/platform/rsn/simulator/events.yml",
    ):
        self._ndef = NetworkUtil.deserialize_network_definition(file(yaml_filename))
        self._pnodes = self._ndef.pnodes

        # registered event listeners: {url: reg_time, ...},
        # where reg_time is the NTP time of (latest) registration.
        # NOTE: for simplicity, we don't keep info about unregistered listeners
        self._reg_event_listeners = {}

        self._event_notifier = EventNotifier()
        # EventGenerator only kept while there are listeners registered
        self._event_generator = None
        self._events_filename = events_filename

    def _start_event_generator_if_listeners(self):
        if not self._event_generator and len(self._reg_event_listeners):
            self._event_generator = EventGenerator(self._event_notifier, self._events_filename)
            self._event_generator.start()
            log.debug("event generator started (%s listeners registered)", len(self._reg_event_listeners))

    def _stop_event_generator_if_no_listeners(self):
        if self._event_generator and not len(self._reg_event_listeners):
            log.debug("event generator stopping (no listeners registered)")
            self._event_generator.stop()
            self._event_generator = None

    def _deactivate_simulator(self):
        """
        Special method only intended to be called for when the simulator is run
        in "embedded" form. See test_oms_simulator for the particular case.
        """
        log.info(
            "_deactivate_simulator called. event_generator=%s; %s listeners registered",
            self._event_generator,
            len(self._reg_event_listeners),
        )
        if self._event_generator:
            self._event_generator.stop()
            self._event_generator = None

    def _enter(self):
        """
        Called when entering any of the CI-OMS interface methods.
        """
        self._dispatch_synthetic_exception()

    def _dispatch_synthetic_exception(self):
        """
        Called by all CI_OMS interface methods to dispatch the
        simulation of connection lost.
        """
        if self._raise_exception:
            msg = "(LC) synthetic exception from CIOMSSimulator"
            log.debug(msg)
            raise Exception(msg)

    def ping(self):
        self._enter()

        return "pong"

    def get_platform_metadata(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        pnode = self._pnodes[platform_id]

        # TODO capture/include appropriate elements
        md = {}
        if pnode.name:
            md["name"] = pnode.name
        if pnode.parent:
            md["parent_platform_id"] = pnode.parent.platform_id

        return {platform_id: md}

    def get_platform_attribute_values(self, platform_id, req_attrs):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        # complete time window until current time:
        to_time = ntplib.system_to_ntp_time(time.time())
        attrs = self._pnodes[platform_id].attrs
        vals = {}
        for attrName, from_time in req_attrs:
            if attrName in attrs:
                attr = attrs[attrName]
                values = generate_values(platform_id, attr.attr_id, from_time, to_time)
                vals[attrName] = values
                # Note: values == [] if there are no values.
            else:
                vals[attrName] = InvalidResponse.ATTRIBUTE_ID

        return {platform_id: vals}

    def get_platform_ports(self, platform_id):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        ports = {}
        for port_id, port in self._pnodes[platform_id].ports.iteritems():
            ports[port_id] = {"state": port.state}

        return {platform_id: ports}

    def turn_on_platform_port(self, platform_id, port_id, src):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if port.state == "ON":
            result = NormalResponse.PORT_ALREADY_ON
            log.warn("port %s in platform %s already turned on." % (port_id, platform_id))
        else:
            port.set_state("ON")
            result = NormalResponse.PORT_TURNED_ON
            log.info("port %s in platform %s turned on." % (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def turn_off_platform_port(self, platform_id, port_id, src):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        port = self._pnodes[platform_id].get_port(port_id)
        if port.state == "OFF":
            result = NormalResponse.PORT_ALREADY_OFF
            log.warn("port %s in platform %s already turned off." % (port_id, platform_id))
        else:
            port.set_state("OFF")
            result = NormalResponse.PORT_TURNED_OFF
            log.info("port %s in platform %s turned off." % (port_id, platform_id))

        return {platform_id: {port_id: result}}

    def set_over_current(self, platform_id, port_id, ma, us, src):
        self._enter()

        if platform_id not in self._pnodes:
            return {platform_id: InvalidResponse.PLATFORM_ID}

        if port_id not in self._pnodes[platform_id].ports:
            return {platform_id: {port_id: InvalidResponse.PORT_ID}}

        # OK, but we don't do anything else here, just accept.
        result = NormalResponse.PORT_SET_OVER_CURRENT

        return {platform_id: {port_id: result}}

    def _validate_event_listener_url(self, url):
        """
        Does a basic, static validation of the url.
        """
        # TODO implement it; for now always returning True
        return True

    def register_event_listener(self, url):
        self._enter()

        # NOTE: event_types was previously a parameter to this operation. To
        # minimize changes in the code, I introduced an 'ALL' event type to
        # be used here explicitly.
        event_type = "ALL"

        log.debug("register_event_listener called: url=%r", url)

        if not self._validate_event_listener_url(url):
            return {url: InvalidResponse.EVENT_LISTENER_URL}

        if not url in self._reg_event_listeners:
            # create entry for this new url
            reg_time = self._event_notifier.add_listener(url, event_type)
            self._reg_event_listeners[url] = reg_time
            log.info("registered url=%r", url)
        else:
            # already registered:
            reg_time = self._reg_event_listeners[url]

        self._start_event_generator_if_listeners()

        return {url: reg_time}

    def unregister_event_listener(self, url):
        self._enter()

        # NOTE: event_types was previously a parameter to this operation. To
        # minimize changes in the code, I introduced an 'ALL' event type to
        # be used here explicitly.
        event_type = "ALL"

        log.debug("unregister_event_listener called: url=%r", url)

        if not url in self._reg_event_listeners:
            return {url: 0}

        #
        # registered, so remove it
        #
        unreg_time = self._event_notifier.remove_listener(url, event_type)
        del self._reg_event_listeners[url]

        log.info("unregistered url=%r", url)

        self._stop_event_generator_if_no_listeners()

        return {url: unreg_time}

    def get_registered_event_listeners(self):
        self._enter()

        return self._reg_event_listeners

    def generate_test_event(self, event):
        self._enter()

        if self._event_generator:  # there are listeners registered.
            # copy event and include the additional fields:
            event_instance = event.copy()
            event_instance["test_event"] = True
            timestamp = ntplib.system_to_ntp_time(time.time())
            if "timestamp" not in event_instance:
                event_instance["timestamp"] = timestamp
            if "first_time_timestamp" not in event_instance:
                event_instance["first_time_timestamp"] = timestamp
            # simply notify listeners right away
            self._event_notifier.notify(event_instance)
            return True

        else:  # there are *no* listeners registered.
            return False