示例#1
0
class SensorRepository:
    """A sensor repository for a particular IPMI device"""
    def __init__(self, server_key, enable_thermal=False):
        self._server_key = server_key

        self._graph_ref = GraphReference()
        self._sensor_file_locks = SensorFileLocks()

        self._sensor_dir = os.path.join(get_temp_workplace_dir(),
                                        str(server_key), "sensor_dir")

        self._sensors = {}

        with self._graph_ref.get_session() as session:
            sensors = GraphReference.get_asset_sensors(session, server_key)
            for sensor_info in sensors:
                sensor = Sensor(
                    self._sensor_dir,
                    server_key,
                    sensor_info,
                    self._sensor_file_locks,
                    graph_ref=self._graph_ref,
                )
                self._sensors[sensor.name] = sensor

        if enable_thermal:
            self._load_thermal = True

            if not os.path.isdir(self._sensor_dir):
                os.mkdir(self._sensor_dir)

            for s_name in self._sensors:
                self._sensors[s_name].set_to_defaults()

    def __str__(self):

        repo_str = []
        repo_str.append("Sensor Repository for Server {}".format(
            self._server_key))
        repo_str.append(
            " - files for sensor readings are located at '{}'".format(
                self._sensor_dir))

        return "\n\n".join(
            repo_str +
            list(map(lambda sn: str(self._sensors[sn]), self._sensors)))

    def enable_thermal_impact(self):
        """Set thermal event switch """
        list(
            map(lambda sn: self._sensors[sn].enable_thermal_impact(),
                self._sensors))

    def disable_thermal_impact(self):
        """Clear thermal event switch"""
        list(
            map(lambda sn: self._sensors[sn].disable_thermal_impact(),
                self._sensors))

    def shut_down_sensors(self):
        """Set all sensors to offline"""
        for s_name in self._sensors:
            sensor = self._sensors[s_name]
            if sensor.group != SensorGroups.temperature:
                sensor.set_to_off()

        self.disable_thermal_impact()

    def power_up_sensors(self):
        """Set all sensors to online"""
        for s_name in self._sensors:
            sensor = self._sensors[s_name]
            if sensor.group != SensorGroups.temperature:
                sensor.set_to_defaults()
        self.enable_thermal_impact()

    def get_sensor_by_name(self, name):
        """Get a specific sensor by name"""
        return self._sensors[name]

    def get_sensors_by_group(self, group):
        """Get sensors by group name (temperature, fan etc) """

        sensors_in_group = filter(lambda k: self._sensors[k].group == group,
                                  self._sensors)
        return list(map(lambda k: self._sensors[k], sensors_in_group))

    def adjust_thermal_sensors(self, old_ambient, new_ambient):
        """Indicate an ambient update"""

        for s_name in self._sensors:
            sensor = self._sensors[s_name]
            if sensor.group == SensorGroups.temperature:
                with self._sensor_file_locks.get_lock(sensor.name):

                    old_sensor_value = int(sensor.sensor_value)
                    new_sensor_value = (old_sensor_value - old_ambient +
                                        new_ambient
                                        if old_sensor_value else new_ambient)

                    logger.debug(
                        "Sensor:[%s] updated from %s° to %s° due to ambient changes (%s°)->(%s°)",
                        sensor.name,
                        old_sensor_value,
                        new_sensor_value,
                        old_ambient,
                        new_ambient,
                    )

                    sensor.sensor_value = int(new_sensor_value)

        if self._load_thermal is True:
            self._load_thermal = False

            for s_name in self._sensors:
                self._sensors[s_name].start_thermal_impact()

    @property
    def sensor_dir(self):
        """Get temp IPMI state dir"""
        return self._sensor_dir

    @property
    def sensors(self):
        """Get all sensors in a sensor repo"""
        return self._sensors

    @property
    def server_key(self):
        """Get key of the server repo belongs to"""
        return self._server_key

    def stop(self):
        """Closes all the open connections"""
        self._graph_ref.close()
示例#2
0
class IStateManager:
    """Base class for all the state managers """

    redis_store = None

    class PowerStateReason(Enum):
        """Describes reason behind asset power state"""

        ac_restored = 1
        ac_lost = 2
        turned_on = 3
        turned_off = 4
        signal_on = 5
        signal_off = 6

    def __init__(self, asset_info):
        self._graph_ref = GraphReference()
        self._asset_key = asset_info["key"]
        self._asset_info = asset_info

    def close_connection(self):
        """Close bolt driver connections"""
        self._graph_ref.close()

    @property
    def key(self) -> int:
        """Asset Key """
        return self._asset_key

    @property
    def redis_key(self) -> str:
        """Asset key in redis format as '{key}-{type}' """
        return "{}-{}".format(str(self.key), self.asset_type)

    @property
    def asset_type(self) -> str:
        """Asset Type """
        return self._asset_info["type"]

    @property
    def power_on_ac_restored(self) -> bool:
        """Returns true if asset is configured to power up when AC
        is restored (voltage is above lower threshold)"""
        return (
            self._asset_info["powerOnAc"] if "powerOnAc" in self._asset_info else True
        )

    @property
    def power_usage(self):
        """Normal power usage in AMPS when powered up"""
        if self.input_voltage and "powerConsumption" in self._asset_info:
            return self._asset_info["powerConsumption"] / self.input_voltage

        return 0.0

    @property
    def draw_percentage(self) -> float:
        """How much power the asset draws"""
        return self._asset_info["draw"] if "draw" in self._asset_info else 1

    @property
    def power_consumption(self):
        """How much power this device consumes (wattage)"""
        return (
            self._asset_info["powerConsumption"]
            if "powerConsumption" in self._asset_info
            else 0
        )

    @property
    def asset_info(self) -> dict:
        """Get information associated with the asset"""
        return self._asset_info

    @property
    def load(self):
        """Get current load stored in redis (in AMPs)"""
        return float(IStateManager.get_store().get(self.redis_key + ":load"))

    @property
    def wattage(self):
        """Asset wattage (assumes power-source to be 120v)"""
        return self.load * self.input_voltage

    def min_voltage_prop(self):
        """Get minimum voltage required and the poweroff timeout associated with it"""

        if not "minVoltage" in self._asset_info:
            return 0

        return self._asset_info["minVoltage"]

    @property
    def status(self):
        """Operational State

        Returns:
            int: 1 if on, 0 if off
        """
        return int(IStateManager.get_store().get(self.redis_key + ":state"))

    @property
    def input_voltage(self):
        """Asset input voltage in Volts"""
        in_volt = IStateManager.get_store().get(self.redis_key + ":in-voltage")
        return float(in_volt) if in_volt else 0.0

    @property
    def output_voltage(self):
        """Output voltage for the device"""
        return self.input_voltage * self.status

    @property
    def agent(self):
        """Agent instance details (if supported)

        Returns:
            tuple: process id and status of the process (if it's running)
        """
        pid = IStateManager.get_store().get(self.redis_key + ":agent")
        return (
            (int(pid), os.path.exists("/proc/" + pid.decode("utf-8"))) if pid else None
        )

    @record
    @Randomizer.randomize_method()
    def shut_down(self):
        """Implements state logic for graceful power-off event,
        sleeps for the pre-configured time

        Returns:
            int: Asset's status after power-off operation
        """
        if self.status:
            self._sleep_shutdown()
            self._set_state_off()
            return 0
        return self.status

    @record
    @Randomizer.randomize_method()
    def power_off(self):
        """Implements state logic for abrupt power loss

        Returns:
            int: Asset's status after power-off operation
        """
        if self.status:
            self._set_state_off()
            return 0
        return self.status

    @record
    @Randomizer.randomize_method()
    def power_up(self):
        """Implements state logic for power up;
        sleeps for the pre-configured time & resets boot time

        Returns:
            int: Asset's status after power-on operation
        """
        if self._parents_available() and not self.status:
            self._sleep_powerup()
            # update machine start time & turn on
            self._reset_boot_time()
            self._set_state_on()
            return 1
        return self.status

    def _update_input_voltage(self, voltage: float):
        """Set input voltage"""
        voltage = max(voltage, 0)
        IStateManager.get_store().set(self.redis_key + ":in-voltage", voltage)

    def _update_load(self, load: float):
        """Update power load for the asset"""
        load = max(load, 0.0)
        IStateManager.get_store().set(self.redis_key + ":load", load)

    def _sleep_delay(self, delay_type):
        """Sleep for n number of ms determined by the delay_type"""
        if delay_type in self._asset_info:
            time.sleep(self._asset_info[delay_type] / 1000.0)  # ms to sec

    def _sleep_shutdown(self):
        """Hardware-specific shutdown delay"""
        self._sleep_delay("offDelay")

    def _sleep_powerup(self):
        """Hardware-specific powerup delay"""
        self._sleep_delay("onDelay")

    def _set_redis_asset_state(self, state):
        """Update redis value of the asset power status"""
        IStateManager.get_store().set(self.redis_key + ":state", state)

    def _set_state_on(self):
        """Set state to online"""
        self._publish_power(self.status, 1)

    def _set_state_off(self):
        """Set state to offline"""
        self._publish_power(self.status, 0)

    def _publish_power(self, old_state, new_state):
        """Notify daemon of power updates"""
        IStateManager.get_store().publish(
            RedisChannels.state_update_channel,
            json.dumps(
                {"key": self.key, "old_state": old_state, "new_state": new_state}
            ),
        )

    def _reset_boot_time(self):
        """Reset device start time (this redis key/value is used to calculate uptime)
        (see snmppub.lua)
        """
        IStateManager.get_store().set(
            str(self._asset_key) + ":start_time", int(time.time())
        )

    def _check_parents(
        self, keys, parent_down, msg="Cannot perform the action: [{}] parent is off"
    ):
        """Check that redis values pass certain condition

        Args:
            keys (list): Redis keys (formatted as required)
            parent_down (callable): lambda clause
            msg (str, optional): Error message to be printed

        Returns:
            bool: True if parent keys are missing or all parents
                  were verified with parent_down clause
        """
        if not keys:
            return True

        parent_values = IStateManager.get_store().mget(keys)
        pdown_msg = []

        for rkey, rvalue in zip(keys, parent_values):
            if parent_down(rvalue, rkey):
                pdown_msg.append(msg.format(rkey))

        if len(pdown_msg) == len(keys):
            print("\n".join(pdown_msg))
            return False

        return True

    def _parents_available(self):
        """Indicates whether a state action can be performed;
        checks if parent nodes are up & running and all OIDs indicate 'on' status

        Returns:
            bool: True if parents are available
        """

        asset_keys, oid_keys = GraphReference.get_parent_keys(
            self._graph_ref.get_session(), self._asset_key
        )

        # if wall-powered, check the mains
        if not asset_keys and not ISystemEnvironment.power_source_available():
            return False

        state_managers = list(map(self.get_state_manager_by_key, asset_keys))
        min_volt_value = self.min_voltage_prop()

        # check if power is present
        enough_voltage = len(
            list(filter(lambda sm: sm.output_voltage > min_volt_value, state_managers))
        )
        parent_assets_up = len(list(filter(lambda sm: sm.status, state_managers)))

        oid_clause = (
            lambda rvalue, rkey: rvalue.split(b"|")[1].decode()
            == oid_keys[rkey]["switchOff"]
        )
        oids_on = self._check_parents(oid_keys.keys(), oid_clause)

        return (parent_assets_up and enough_voltage and oids_on) or (not asset_keys)

    def __str__(self):
        return (
            "Asset[{0.asset_type}][{0.key}] \n"
            " - Status: {0.status} \n"
            " - Load: {0.load}A\n"
            " - Power Consumption: {0.power_consumption}W \n"
            " - Input Voltage: {0.input_voltage}V\n"
            " - Output Voltage: {0.output_voltage}V\n"
        ).format(self)

    @classmethod
    def get_store(cls):
        """Get redis db handler """
        if not cls.redis_store:
            cls.redis_store = redis.StrictRedis(host="localhost", port=6379)

        return cls.redis_store

    @classmethod
    def _get_assets_states(cls, assets, flatten=True):
        """Query redis store and find states for each asset

        Args:
            flatten(bool): If false, the returned assets in the dict
                           will have their child-components nested
        Returns:
            dict: Current information on assets including their states, load etc.
        """
        asset_keys = assets.keys()

        if not asset_keys:
            return None

        asset_values = cls.get_store().mget(
            list(map(lambda k: "{}-{}:state".format(k, assets[k]["type"]), asset_keys))
        )

        for rkey, rvalue in zip(assets, asset_values):
            asset_state = cls.get_state_manager_by_key(rkey)

            assets[rkey]["status"] = int(rvalue)
            assets[rkey]["load"] = asset_state.load

            if assets[rkey]["type"] == "ups":
                assets[rkey]["battery"] = asset_state.battery_level

            if not flatten and "children" in assets[rkey]:
                # call recursively on children
                assets[rkey]["children"] = cls._get_assets_states(
                    assets[rkey]["children"]
                )

        return assets

    @classmethod
    def get_system_status(cls, flatten=True):
        """Get states of all system components

        Args:
            flatten(bool): If false, the returned assets in the dict
                           will have their child-components nested

        Returns:
            dict: Current information on assets including their states, load etc.
        """
        graph_ref = GraphReference()
        with graph_ref.get_session() as session:

            # cache assets
            assets = GraphReference.get_assets_and_connections(session, flatten)
            return cls._get_assets_states(assets, flatten)

    @classmethod
    @lru_cache(maxsize=32)
    def get_state_manager_by_key(cls, key, supported_assets=None):
        """Infer asset manager from key"""
        from enginecore.state.hardware.room import Asset

        if not supported_assets:
            supported_assets = Asset.get_supported_assets()

        graph_ref = GraphReference()

        with graph_ref.get_session() as session:
            asset_info = GraphReference.get_asset_and_components(session, key)

        sm_mro = supported_assets[asset_info["type"]].StateManagerCls.mro()

        module = ".".join(__name__.split(".")[:-1])  # api module
        return next(filter(lambda x: x.__module__.startswith(module), sm_mro))(
            asset_info
        )

    @classmethod
    def asset_exists(cls, key):
        """Check if asset with the key exists"""
        graph_ref = GraphReference()

        with graph_ref.get_session() as session:
            asset_info = GraphReference.get_asset_and_components(session, key)
            return asset_info is not None

    @classmethod
    def set_play_path(cls, path):
        """Update play folder containing scripts"""

        graph_ref = GraphReference()
        with graph_ref.get_session() as session:
            GraphReference.set_play_path(session, path)

    @classmethod
    def plays(cls):
        """Get plays (user-defined scripts) available for execution
        Returns:
            tuple: list of bash files as well as python scripts
        """

        graph_ref = GraphReference()
        with graph_ref.get_session() as session:
            play_path = GraphReference.get_play_path(session)

        if not play_path:
            return ([], [])

        play_files = [
            f
            for f in os.listdir(play_path)
            if os.path.isfile(os.path.join(play_path, f))
        ]

        is_py_file = lambda f: os.path.splitext(f)[1] == ".py"

        return (
            [os.path.splitext(f)[0] for f in play_files if not is_py_file(f)],
            [os.path.splitext(f)[0] for f in play_files if is_py_file(f)],
        )

    @classmethod
    def execute_play(cls, play_name):
        """Execute a specific play
        Args:
            play_name(str): playbook name
        """

        graph_ref = GraphReference()
        with graph_ref.get_session() as session:

            play_path = GraphReference.get_play_path(session)
        if not play_path:
            return

        file_filter = (
            lambda f: os.path.isfile(os.path.join(play_path, f))
            and os.path.splitext(f)[0] == play_name
        )

        play_file = [f for f in os.listdir(play_path) if file_filter(f)][0]

        subprocess.Popen(
            os.path.join(play_path, play_file),
            stderr=subprocess.DEVNULL,
            close_fds=True,
        )