def reload_model(self, force_snmp_init=True): """Re-create system topology (instantiate assets based on graph ref)""" RECORDER.enabled = False ISystemEnvironment.set_ambient(0) logger.info("Initializing system topology...") self._assets = {} # init state clear_temp() initialize(force_snmp_init) # get system topology assets = self._data_source.get_all_assets() for asset in assets: self._assets[asset["key"]] = Asset.get_supported_assets()[asset["type"]]( asset ).register(self) self._power_iter_handler.start(on_iteration_launched=self._chain_power_events) self._thermal_iter_handler.start( on_iteration_launched=self._chain_thermal_events ) ISystemEnvironment.set_ambient(21) RECORDER.enabled = True
def handle_voltage_get(args): """Action callback for handling voltage get command""" if args["value_only"]: print(ISystemEnvironment.get_voltage()) return print("Voltage: {:.3f}V".format(ISystemEnvironment.get_voltage())) voltage_props = ISystemEnvironment.get_voltage_props() if not voltage_props: print("Voltage properties are not configured yet!") return volt_fluct_info = [] volt_fluct_info.append("Fluctuation Properties:") volt_fluct_info.append("[enabled]: " + str(voltage_props["enabled"])) volt_fluct_info.append("[random distribution method]: " + voltage_props["method"]) if voltage_props["method"] == "gauss": distr_prop_fmt = "mean({mu}), stdev({sigma})" else: distr_prop_fmt = "min({min}), max({max})" volt_fluct_info.append( "[distribution properties]: " + distr_prop_fmt.format(**voltage_props) ) print("\n -> ".join(volt_fluct_info))
def _handle_status_request(self, details): """Get overall system status/details including hardware assets; environment state & play details """ assets = IStateManager.get_system_status(flatten=False) graph_ref = GraphReference() with graph_ref.get_session() as session: stage_layout = GraphReference.get_stage_layout(session) # send system topology and assets' power-interconnections self._write_data( details["client"], ServerToClientRequests.sys_layout, {"assets": assets, "stageLayout": stage_layout}, ) self._write_data( details["client"], ServerToClientRequests.ambient_upd, {"ambient": ISystemEnvironment.get_ambient(), "rising": False}, ) self._write_data( details["client"], ServerToClientRequests.play_list, {"plays": list(itertools.chain(*IStateManager.plays()))}, ) self._write_data( details["client"], ServerToClientRequests.mains_upd, {"mains": ISystemEnvironment.mains_status()}, )
def handle_voltage_set(args): """Action callback for handling voltage set command""" if args["value"] is not None: StateClient.set_voltage(args["value"]) del args["value"] # set voltage fluctuation properties if any are provided if [arg_value for _, arg_value in args.items() if arg_value is not None]: ISystemEnvironment.set_voltage_props(args)
def handle_set_thermal_ambient(kwargs): """Configure thermal properties for room temperature""" if kwargs["event"] and kwargs["pause_at"] and kwargs["rate"]: ISystemEnvironment.set_ambient_props(kwargs) elif kwargs["event"] or kwargs["pause_at"] or kwargs["rate"]: raise argparse.ArgumentTypeError( "Event, pause-at and rate must be supplied") else: StateClient.set_ambient(kwargs["degrees"])
def model_command(asset_group): """Aggregates system modelling cli commands""" model_subp = asset_group.add_subparsers() # creational cmds create_command(model_subp.add_parser("create", help="Create new asset")) update_command( model_subp.add_parser("update", help="Update Asset properties")) ## MISC model commands reload_asset_action = model_subp.add_parser( "reload", help="Reload the system topology (notify daemon of model changes)") # detach & delete an asset by key delete_asset_action = model_subp.add_parser( "delete", help="Remove individual asset by key") delete_asset_action.add_argument("-k", "--asset-key", type=int, required=True) # drop entire system topology drop_system_action = model_subp.add_parser( "drop", help="Delete/drop all the system components") # link 2 assets together power_asset_action = model_subp.add_parser( "power-link", help="Create/Delete a power link between 2 assets") power_asset_action.add_argument( "-s", "--source-key", type=int, required=True, help="Key of an asset that POWERS dest. asset", ) power_asset_action.add_argument( "-d", "--dest-key", type=int, required=True, help="Key of the asset powered by the source-key", ) power_asset_action.add_argument("-r", "--remove", action="store_true", help="Delete power conneciton if exists") reload_asset_action.set_defaults( func=lambda args: ISystemEnvironment.reload_model()) delete_asset_action.set_defaults( func=lambda args: sys_modeler.delete_asset(args["asset_key"])) power_asset_action.set_defaults(func=handle_link) drop_system_action.set_defaults(func=lambda args: sys_modeler.drop_model())
def handle_configure_randomizer(args, conf_rand_arguments): """Callback for CLI command for configuring randomizer""" # list all the randoptions if args["list"]: amb_props = ISystemEnvironment.get_ambient_props() print( "Ambient random arguments: range from {start} to {end}".format(**amb_props) ) if not args["asset_key"]: return del conf_rand_arguments[conf_rand_arguments.index("ambient")] server_manager = get_server_state_manager(args["asset_key"]) print("Server:[{0.key}] random arguments:".format(server_manager)) for rand_opt in conf_rand_arguments: s_prop = IBMCServerStateManager.StorageRandProps[rand_opt.replace("-", "_")] opt_prop = server_manager.get_storage_radnomizer_prop(s_prop) print( " -- {}: range from {} to {}".format(rand_opt, opt_prop[0], opt_prop[1]) ) return # configure randargs - validate cli options if not args["start"] or not args["end"] or not args["option"]: raise argparse.ArgumentTypeError( "Must provide rand option, start & end range values!" ) # update ambient option if args["option"] == "ambient": ISystemEnvironment.set_ambient_props(args) return # update one of the server disk options server_manager = get_server_state_manager(args["asset_key"]) server_manager.set_storage_randomizer_prop( IBMCServerStateManager.StorageRandProps[args["option"].replace("-", "_")], slice(args["start"], args["end"]), )
def handle_get_thermal_ambient(kwargs): """Print some general information about ambient configurations""" if kwargs["value_only"]: print(ISystemEnvironment.get_ambient()) else: print("Ambient: {}° ".format(ISystemEnvironment.get_ambient())) ambient_props = ISystemEnvironment.get_ambient_props() if not ambient_props: print("Ambient event properties are not configured yet!") return prop_fmt = "{degrees}°/{rate} sec, until {pauseAt}° is reached" print("AC settings:") print(" -> [online] : decrease by " + prop_fmt.format(**ambient_props["up"])) print(" -> [offline] : increase by " + prop_fmt.format(**ambient_props["down"]))
def _handle_rand_act(self, details): """Handle perform random actions request""" rand_session_specs = details["payload"] assets = IStateManager.get_system_status(flatten=True) nap = None # filter out assets if range is provided if rand_session_specs["asset_keys"]: assets = list( filter(lambda x: x in rand_session_specs["asset_keys"], assets) ) if not assets and not 0 in rand_session_specs["asset_keys"]: logger.warning("No assets selected for random actions") return state_managers = list(map(IStateManager.get_state_manager_by_key, assets)) if ( not rand_session_specs["asset_keys"] or 0 in rand_session_specs["asset_keys"] ): state_managers.append(ISystemEnvironment()) if rand_session_specs["nap_time"]: def nap_calc(): nap_time = lambda: rand_session_specs["nap_time"] if rand_session_specs["min_nap"]: nap_time = lambda: random.randrange( rand_session_specs["min_nap"], rand_session_specs["nap_time"] ) time.sleep(nap_time()) nap = nap_calc rand_t = threading.Thread( target=Randomizer.randact, args=(state_managers,), kwargs={ "num_iter": rand_session_specs["count"], "seconds": rand_session_specs["seconds"], "nap": nap, }, name="[#] Randomizer", ) rand_t.daemon = True rand_t.start()
def get_status(**kwargs): """Retrieve power states of the assets Args: **kwargs: Command line options """ #### one asset #### if kwargs["asset_key"] and kwargs["load"]: state_manager = IStateManager.get_state_manager_by_key(kwargs["asset_key"]) if kwargs["value_only"]: print(state_manager.load) else: print( "{}-{} : {}".format( state_manager.key, state_manager.asset_type, state_manager.load ) ) return elif kwargs["asset_key"] and kwargs["agent"]: state_manager = IStateManager.get_state_manager_by_key(kwargs["asset_key"]) agent_info = state_manager.agent if agent_info: msg = "running" if agent_info[1] else "not running" if kwargs["value_only"]: print(int(agent_info[1])) else: print( "{}-{} : pid[{}] is {}".format( state_manager.key, state_manager.asset_type, agent_info[0], msg ) ) else: print( "{}-{} is not running any agents!".format( state_manager.key, state_manager.asset_type ) ) return elif kwargs["asset_key"]: state_manager = IStateManager.get_state_manager_by_key(kwargs["asset_key"]) if kwargs["value_only"]: print(state_manager.status) else: print( "{}-{} : {}".format( state_manager.key, state_manager.asset_type, state_manager.status ) ) return elif kwargs["mains"]: mains_status = ISystemEnvironment.mains_status() if kwargs["value_only"]: print(mains_status) else: print("Wallpower status:", mains_status) return ##### list states ##### assets = IStateManager.get_system_status() # json format if kwargs["json"]: print(json.dumps(assets, indent=4)) # monitor state with curses elif kwargs["monitor"]: stdscr = curses.initscr() curses.noecho() curses.cbreak() try: curses.start_color() curses.use_default_colors() for i in range(0, curses.COLORS): curses.init_pair(i, i, -1) while True: status_table_format(assets, stdscr) time.sleep(kwargs["watch_rate"]) assets = IStateManager.get_system_status() except KeyboardInterrupt: pass finally: curses.echo() curses.nocbreak() curses.endwin() # human-readable table else: status_table_format(assets)
def create_command(create_asset_group): """Model creation (cli endpoints to initialize system topology)""" # parent will contain args shared by all the asset types # (such as key, [x,y] positions, name etc.) create_asset_parent = argparse.ArgumentParser(add_help=False) create_asset_parent.add_argument( "-k", "--asset-key", type=int, required=True, help="Unique asset key (must be <= 9999)", ) create_asset_parent.add_argument("--on-delay", type=int, help="Power on delay in ms", default=0) create_asset_parent.add_argument("--off-delay", type=int, help="Power on delay in ms", default=0) create_asset_parent.add_argument( "-x", type=int, help="x - asset position on the dashboard", default=0) create_asset_parent.add_argument( "-y", type=int, help="y - asset position on the dashboard", default=0) create_asset_parent.add_argument( "--power-on-ac", dest="power_on_ac", action="store_true", help="Power up on AC restored", ) create_asset_parent.add_argument( "--no-power-on-ac", dest="power_on_ac", action="store_false", help="Don't power up when AC is restored", ) create_asset_parent.add_argument("-n", "--name", help="Name displayed on the UI") create_asset_parent.set_defaults(new_asset=True, power_on_ac=True) create_volt_parent = argparse.ArgumentParser(add_help=False) create_volt_parent.add_argument( "--min-voltage", type=float, help="Voltage value below/at which asset stops functioning", default=90.0, ) # snmp group parent will contain snmp-specific args create_snmp_parent = argparse.ArgumentParser(add_help=False) create_snmp_parent.add_argument("--host", type=str, default="localhost", help="SNMP interface host") create_snmp_parent.add_argument("--port", type=int, required=True, help="SNMP interface port") create_snmp_parent.add_argument( "--snmp-preset", type=str, help="Vendor-specific asset configurations") create_snmp_parent.add_argument( "--serial-number", type=str, help="Serial number of a simulated SNMP device") create_snmp_parent.add_argument( "--mac-address", type=str, help="MAC address of a simulated SNMP device") create_snmp_parent.add_argument( "--interface", type=str, help="Network interface attached to SNMP device") create_snmp_parent.add_argument("--mask", type=str, help="Net mask of interface") # server group create_server_parent = argparse.ArgumentParser(add_help=False) create_server_parent.add_argument("--domain-name", help="VM domain name") # power consuming assets group create_power_parent = argparse.ArgumentParser(add_help=False) create_power_parent.add_argument( "--power-source", type=int, default=ISystemEnvironment.wallpower_volt_standard()) create_power_parent.add_argument( "--power-consumption", required=True, type=int, help="Power consumption in Watts", ) ## > Add type-specific args < ## create_subp = create_asset_group.add_subparsers() ## OUTLET create_outlet_action = create_subp.add_parser( "outlet", help="Create a Simple outlet asset", parents=[create_asset_parent]) ## PDU create_pdu_action = create_subp.add_parser( "pdu", help="Create PDU asset", parents=[create_asset_parent, create_volt_parent, create_snmp_parent], ) ## UPS create_ups_action = create_subp.add_parser( "ups", help="Create UPS asset", parents=[ create_asset_parent, create_snmp_parent, get_ups_command_parent() ], ) create_ups_action.add_argument( "--power-source", help="Asset Voltage", type=int, default=ISystemEnvironment.wallpower_volt_standard(), ) create_ups_action.add_argument( "--power-consumption", type=int, help="""Power consumption in Watts (how much UPS draws when not powering anything)""", default=24, ) ## SERVER create_server_action = create_subp.add_parser( "server", help="Create a server asset (VM)", parents=[ create_asset_parent, create_volt_parent, create_server_parent, create_power_parent, ], ) create_server_action.add_argument( "--psu-num", type=int, default=1, help="Number of PSUs installed in the server") create_server_action.add_argument( "--psu-load", nargs="+", type=float, help="""PSU(s) load distribution (the downstream power is multiplied by the value, e.g. for 2 PSUs if '--psu-load 0.5 0.5', load is divided equally) \n""", ) create_server_action.add_argument( "--psu-power-consumption", nargs="+", type=int, default=6, help="""Power consumption of idle PSU \n""", ) create_server_action.add_argument( "--psu-power-source", nargs="+", type=int, default=ISystemEnvironment.wallpower_volt_standard(), help="""PSU Voltage \n""", ) ## SERVER-BMC create_server_bmc_action = create_subp.add_parser( "server-bmc", help="Create a server asset (VM) that supports IPMI interface", parents=[ create_asset_parent, create_volt_parent, create_server_parent, create_power_parent, ], ) create_server_bmc_action.add_argument( "--user", type=str, default="ipmiusr", help="BMC-enabled server: IPMI admin user", ) create_server_bmc_action.add_argument( "--password", type=str, default="test", help="BMC-enabled server: IPMI user password", ) create_server_bmc_action.add_argument("--host", type=str, default="localhost", help="IPMI interface host") create_server_bmc_action.add_argument("--port", type=int, default=9001, help="IPMI interface port") create_server_bmc_action.add_argument( "--interface", type=str, default="", help="Network interface attached to the server", ) create_server_bmc_action.add_argument( "--vmport", type=int, default=9002, help="IPMI serial VM interface for channel 15 (the system interface)", ) create_server_bmc_action.add_argument( "--storcli-port", type=int, default=50000, help="Storcli websocket port used to establish a connection with a vm", ) create_server_bmc_action.add_argument( "--sensor-def", type=str, help="""File containing sensor definitions (defaults to sensors.json file in enginecore/enginecore/model/presets)""", ) create_server_bmc_action.add_argument( "--storage-def", type=str, help="""File containing storage definitions (defaults to storage.json file in enginecore/enginecore/model/presets) """, ) create_server_bmc_action.add_argument( "--storage-states", type=str, help="""File containing storage state mappings (.JSON) """, ) ## STATIC create_static_action = create_subp.add_parser( "static", help="Add static (dummy) asset", parents=[create_asset_parent, create_volt_parent, create_power_parent], ) create_static_action.add_argument( "--img-url", help="URL of the image displayed on the frontend") ## LAMP create_lamp_action = create_subp.add_parser( "lamp", help="Used for power demonstrations", parents=[create_asset_parent, create_volt_parent, create_power_parent], ) create_outlet_action.set_defaults( validate=lambda args: validate_key(args["asset_key"]), func=lambda args: sys_modeler.create_outlet(args["asset_key"], args), ) create_pdu_action.set_defaults( validate=lambda args: validate_key(args["asset_key"]), func=lambda args: sys_modeler.create_pdu(args["asset_key"], args), ) create_ups_action.set_defaults( validate=lambda args: validate_key(args["asset_key"]), func=lambda args: sys_modeler.create_ups(args["asset_key"], args), ) create_server_action.set_defaults( validate=lambda args: [validate_key(args["asset_key"]), validate_server(args)], func=lambda args: sys_modeler.create_server(args["asset_key"], args), ) create_server_bmc_action.set_defaults( validate=lambda args: [validate_key(args["asset_key"])], func=lambda args: sys_modeler.create_server( args["asset_key"], args, server_variation=sys_modeler.ServerVariations.ServerWithBMC, ), ) create_static_action.set_defaults( validate=lambda args: validate_key(args["asset_key"]), func=lambda args: sys_modeler.create_static(args["asset_key"], args), ) create_lamp_action.set_defaults( validate=lambda args: validate_key(args["asset_key"]), func=lambda args: sys_modeler.create_lamp(args["asset_key"], args), )
def power_command(power_group): """CLI endpoints for managing assets' power states & wallpower""" power_subp = power_group.add_subparsers() # Manage power of individual assets power_up_action = power_subp.add_parser( "up", help="Power up a particular component/asset" ) power_up_action.add_argument("-k", "--asset-key", type=int, required=True) power_down_action = power_subp.add_parser( "down", help="Power down a particular component/asset" ) power_down_action.add_argument("-k", "--asset-key", type=int, required=True) power_down_action.add_argument( "--hard", help="Enable abrupt poweroff instead of shutdown", dest="hard", action="store_true", ) # Wallpower management power_outage_action = power_subp.add_parser( "outage", help="Simulate complete power loss" ) power_restore_action = power_subp.add_parser( "restore", help="Restore mains power after outage" ) # Voltage system-wide voltage_action_group = power_subp.add_parser( "voltage", help="Manage systems voltage behavior" ) voltage_subp = voltage_action_group.add_subparsers() get_voltage_action = voltage_subp.add_parser( "get", help="Query current voltage configurations" ) get_voltage_action.add_argument( "--value-only", help="Return voltage value only (omit details of voltage behaviour)", action="store_true", ) set_voltage_action = voltage_subp.add_parser( "set", help="Update voltage value/voltage settings" ) set_voltage_action.add_argument( "--value", type=float, help="Update voltage value (in Volts)" ) set_voltage_action.add_argument( "--mu", type=float, help="Mean for gaussian random method for voltage fluctuations", ) set_voltage_action.add_argument( "--sigma", type=float, help="Standard deviation for gaussian random method for voltage fluctuations", ) set_voltage_action.add_argument( "--min", type=float, help="Min volt value for uniform random method for voltage fluctuations", ) set_voltage_action.add_argument( "--max", type=float, help="Max volt value for uniform random method for voltage fluctuations", ) set_voltage_action.add_argument( "--method", choices=ISystemEnvironment.voltage_random_methods(), help="Max volt value for uniform random method for voltage fluctuations", ) set_voltage_action.add_argument( "--enable-fluctuation", dest="enabled", action="store_true" ) set_voltage_action.add_argument( "--disable-fluctuation", dest="enabled", action="store_false" ) # CLI action callbacks power_outage_action.set_defaults(func=lambda _: StateClient.power_outage()) power_restore_action.set_defaults(func=lambda _: StateClient.power_restore()) power_up_action.set_defaults( func=lambda args: manage_state(args["asset_key"], lambda a: a.power_up()) ) power_down_action.set_defaults( hard=False, # abrupt shutdown if False by default func=lambda args: manage_state( args["asset_key"], lambda a: a.power_off() if args["hard"] else a.shut_down(), ), ) set_voltage_action.set_defaults(func=handle_voltage_set) get_voltage_action.set_defaults(func=handle_voltage_get)
def _handle_voltage_request(self, details): """ "Handle voltage update""" ISystemEnvironment.set_voltage(details["payload"]["voltage"])
def _handle_ambient_request(self, details): """ "Handle ambient changes request""" ISystemEnvironment.set_ambient(details["payload"]["degrees"])
def _handle_mains_request(self, details): """Wallpower update request""" if details["payload"]["mains"] == 0: ISystemEnvironment.power_outage() else: ISystemEnvironment.power_restore()