Source code for exos.api.dm

# ******************************************************
#
#  Copyright (c) Extreme Networks Inc. 2014, 2021
#  All rights reserved
#
# ******************************************************

import _exos_ext_dm as ext
import logging
import functools
from exos.api.util import Observable, NotifyBase, Enum

try:
    import cPickle as pickle
except ImportError:
    import pickle

# Bring the platform_id stuff up.  This isn't public (yet).
from _exos_ext_dm import (  # noqa: F401
    get_platform_id,
    HAL_PLATFORM_ID_PC_STACKABLE,
    HAL_PLATFORM_ID_PC_CHASSIS,
    HAL_PLATFORM_ID_PC_STACKABLE_STACK,
    HAL_PLATFORM_ID_EXTENT,
    # Process names
    THTTPD_PROCESS_NAME,
    FDB_PROCESS_NAME,
    OTM_PROCESS_NAME,
    VLAN_PROCESS_NAME,
)

logger = logging.getLogger("pyapi")

# Message IDs we'll use while checkpointing.
DM_CHKPT_PARTIAL = 0x1000

#: DM Process states.
ProcessState = Enum(
    [
        "UNKNOWN",
        "FAIL",
        "STOPPED",
        "STOPPING",
        "STARTED",
        "BOOTING",
        "CONNECT",
        "DBSYNC",
        "LOADCFG",
        "READY",
        "DELETED",
    ]
)
ProcessState._mapping = {
    ext.DM_PROCESS_STATE_FAIL: ProcessState.FAIL,
    ext.DM_PROCESS_STATE_STOPPED: ProcessState.STOPPED,
    ext.DM_PROCESS_STATE_STOPPING: ProcessState.STOPPING,
    ext.DM_PROCESS_STATE_STARTED: ProcessState.STARTED,
    ext.DM_PROCESS_STATE_BOOTING: ProcessState.BOOTING,
    ext.DM_PROCESS_STATE_CONNECT: ProcessState.CONNECT,
    ext.DM_PROCESS_STATE_DBSYNC: ProcessState.DBSYNC,
    ext.DM_PROCESS_STATE_LOADCFG: ProcessState.LOADCFG,
    ext.DM_PROCESS_STATE_READY: ProcessState.READY,
    ext.DM_PROCESS_STATE_DELETED: ProcessState.DELETED,
}


#: DM Node states.
NodeState = Enum(
    [
        "UNKNOWN",
        "PRIMARY",
        "BACKUP",
        "STANDBY",
    ]
)
NodeState._mapping = {
    ext.DM_NODE_STATE_PRIMARY: NodeState.PRIMARY,
    ext.DM_NODE_STATE_BACKUP: NodeState.BACKUP,
    ext.DM_NODE_STATE_STANDBY: NodeState.STANDBY,
}


class DMNotify(NotifyBase):
    """Notification class for DM."""

    @classmethod
    def _map_process_state(cls, state):
        return ProcessState.map_enum(state, ProcessState.UNKNOWN)

    @classmethod
    def _map_node_state(cls, state):
        return NodeState.map_enum(state, NodeState.UNKNOWN)

    @Observable()
    def card_up(self, **kwds):
        """A card was joined the switch.  The card is identified by
        *card_id*."""
        return kwds

    @Observable()
    def card_down(self, **kwds):
        """A card was left the switch.  The card is identified by
        *card_id*."""
        return kwds

    @Observable()
    def process_state_update(self, **kwds):
        """The state of an EXOS process *process_name* has changed to *state*.  State is a
        value of :class:ProcessState."""
        kwds["state"] = self._map_process_state(kwds["state"])
        return kwds

    @Observable()
    def node_state_update(self, **kwds):
        """This node has been instructed to change to *state*."""
        kwds["state"] = self._map_node_state(kwds["state"])
        return kwds

    @Observable()
    def checkpointing_update(self, **kwds):
        """The *state* of checkpointing has changed.  If state is True,
        the process is expected to be syncing with its backup.  If False,
        it should not."""
        kwds["state"] = bool(kwds["state"])
        return kwds

    @Observable()
    def config_update(self, **kwds):
        """The *sys_name* specifies the new device name of the switch"""
        return kwds


#: Collection of DM Observables.
notify = DMNotify()

# Tell our extension to send us notifications.  This doesn't happen in an init
# because DM is always ready.
ext.set_notify_callbacks(notify)


[docs]def is_capability_supported(capability): """Return ``True`` if this switch supports the given *capability*, otherwise ``False``. *capability* is a string. If it is not a recognized, a :class:`ValueError` is raised.""" return True if ext.is_capability_supported(capability) else False
def is_chassis(): """Return True if we are running on a chassis.""" return ext.get_platform_type() == ext.PLATFORM_TYPE_CHASSIS
[docs]def is_stackable(): """Return ``True`` if we are running on a stackable. However, stacking may not be enabled.""" return ext.get_platform_type() == ext.PLATFORM_TYPE_STACKABLE
[docs]def is_primary(): """Return ``True`` if this switch is the primary.""" return True if ext.am_i_primary() else False
[docs]def is_backup(): """Return ``True`` if this switch is the backup.""" return True if ext.am_i_backup() else False
[docs]def is_standby(): """Return ``True`` if this switch is a standby.""" return True if ext.am_i_standby() else False
[docs]def is_checkpointing(): """Return ``True`` if this switch is ready to checkpoint data.""" return True if ext.is_checkpointing_on() else False
[docs]class SlotProperties(object): @property def self(self): """The current switch's slot number.""" return ext.get_slot_self() @property def first(self): """The first valid slot number.""" return ext.get_slot_first() @property def last(self): """The last valid slot number.""" return ext.get_slot_last() @property def first_io(self): return ext.get_slot_first_io() @property def last_io(self): return ext.get_slot_last_io()
#: Collection of slot number properties. This is a singleton instance of #: :class:`SlotProperties`. slot = SlotProperties()
[docs]def get_sysname(): """Return the switch name""" return ext.get_sysname()
[docs]def get_process_state(process_name): """Get the `ProcessState` of *process_name*.""" return ProcessState.map_enum(ext.get_process_state(process_name), ProcessState.UNKNOWN)
def set_process_state(process_name, process_state): """Set the *process_state" of *process_name*.""" dm_process_state = ProcessState.map_val(process_state, ext.DM_PROCESS_STATE_UNKNOWN) return ProcessState.map_enum(ext.set_process_state(process_name, dm_process_state), ProcessState.UNKNOWN) def _call_on(callfn, fn, *args, **kwds): if not callable(fn): raise TypeError("Function is not callable.") # There's some inconsistency between exceptions from pickle and cPickle. # Hack around it here with a try/except. # http://bugs.python.org/issue1457119 try: msg = pickle.dumps(functools.partial(fn, *args, **kwds)) except TypeError as e: raise pickle.PicklingError(str(e)) return True if callfn(DM_CHKPT_PARTIAL, msg) == 0 else False
[docs]def call_on_primary(fn, *args, **kwds): """Call *fn* on the primary with *args* and *kwds*. *fn*, *args*, *kwds* must be pickle-able. If not, a :class:`PicklingError` is raised. ``True`` is returned if the message was sent. """ return _call_on(ext.write_primary, fn, *args, **kwds)
[docs]def call_on_backup(fn, *args, **kwds): """Call *fn* on the backup with *args* and *kwds*. *fn*, *args*, *kwds* must be pickle-able. If not, a :class:`PicklingError` is raised. ``True`` is returned if the message was sent. """ return _call_on(ext.write_backup, fn, *args, **kwds)
[docs]def call_on_standby(slot, fn, *args, **kwds): """Call *fn* on the standby with *args* and *kwds*. *fn*, *args*, *kwds* must be pickle-able. If not, a :class:`PicklingError` is raised. ``True`` is returned if the message was sent. """ # Closure to hold slot def call_on_standby_slot(fn, *args, **kwds): return ext.write_standby(slot, fn, *args, **kwds) return _call_on(call_on_standby_slot, fn, *args, **kwds)
[docs]def call_on_standbys(fn, *args, **kwds): """Call *fn* on all standbys with *args* and *kwds*. *fn*, *args*, *kwds* must be pickle-able. If not, a :class:`PicklingError` is raised. ``True`` is returned if the message was sent. """ return _call_on(ext.bcast_standby, fn, *args, **kwds)
def _chkpt_callback(msg_id, msg): """Private function to handle checkpoint messages. We'll try to de-pickle them and call the result. """ if msg_id == DM_CHKPT_PARTIAL: try: fn = pickle.loads(msg) except Exception: logger.exception("Unable to load checkpoint message") try: fn() except Exception: logger.exception("Failure in checkpoint callback") else: logger.error("Unknown checkpoint msg_id %d", msg_id) # Set the checkpointing callback to our private function above. This allows # call_on_backup(), etc. to magically work. ext.set_checkpoint_callback(_chkpt_callback)