示例#1
0
 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))
示例#2
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='mi/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] = {'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}
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

    @classmethod
    def x_exit_inactivity(cls, inactivity_period):
        """
        will fill out if needed.
        """
        pass

    def __init__(self, yaml_filename='mi/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
        self._mission_flags = ['pause', 'returntohome', 'returntodock']

        # 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_over_current(self, platform_id, port_id, milliamps, microseconds, 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)
        return {platform_id: {port_id: NormalResponse.OVER_CURRENT_SET}}

    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 get_available_missions(self, platform_id):
        self._enter()

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

        return {platform_id: self._pnodes[platform_id].missions.keys()}

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

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

        return {platform_id: 'Moving'}


    def start_mission(self, platform_id, mission_name, src):
        self._enter()

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

        if mission_name not in self._pnodes[platform_id].missions:
            return {platform_id: InvalidResponse.MISSION_ID}

        return {platform_id: NormalResponse.MISSION_STARTED}


    def stop_mission(self, platform_id, flag, src):
        self._enter()

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

        return {platform_id: NormalResponse.MISSION_STOPPED}


    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}
 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))
示例#5
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='mi/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] = {'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}