1#!/usr/bin/env python3 2# 3# Copyright 2019 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import importlib 18import logging 19 20from acts import tracelogger 21 22MOBLY_CONTROLLER_CONFIG_NAME = 'PduDevice' 23ACTS_CONTROLLER_REFERENCE_NAME = 'pdu_devices' 24 25 26def create(configs): 27 """Creates a PduDevice for each config in configs. 28 29 Args: 30 configs: List of configs from PduDevice field. 31 Fields: 32 device: a string "<brand>.<model>" that corresponds to module 33 in pdu_lib/ 34 host: a string of the device ip address 35 username (optional): a string of the username for device sign-in 36 password (optional): a string of the password for device sign-in 37 Return: 38 A list of PduDevice objects. 39 """ 40 if configs: 41 pdus = [] 42 for config in configs: 43 device = config.get('device') 44 if not device: 45 raise PduError("Config must provide a device") 46 47 host = config.get('host') 48 if not device: 49 raise PduError("Config must provide a host ip address") 50 username = config.get('username') 51 password = config.get('password') 52 pdu = _create_device(device, host, username, password) 53 pdus.append(pdu) 54 return pdus 55 56 57def destroy(pdu_list): 58 """Ensure any connections to devices are closed. 59 60 Args: 61 pdu_list: A list of PduDevice objects. 62 """ 63 for pdu in pdu_list: 64 pdu.close() 65 66 67def get_info(pdu_list): 68 """Retrieves info from a list of PduDevice objects. 69 70 Args: 71 pdu_list: A list of PduDevice objects. 72 Return: 73 A list containing a dictionary for each PduDevice, with keys: 74 'host': a string of the device ip address 75 'username': a string of the username 76 'password': a string of the password 77 """ 78 info = [] 79 for pdu in pdu_list: 80 info.append({ 81 'host': pdu.host, 82 'username': pdu.username, 83 'password': pdu.password 84 }) 85 return info 86 87 88def _create_device(device, host, username, password): 89 """Factory method that returns an instance of PduDevice implementation 90 based on the device string. 91 """ 92 module_name = 'acts.controllers.pdu_lib.' + device 93 module = importlib.import_module(module_name) 94 return module.PduDevice(host, username, password) 95 96 97def get_pdu_port_for_device(device_pdu_config, pdus): 98 """Retrieves the pdu object and port of that PDU powering a given device. 99 This is especially necessary when there are multilpe devices on a single PDU 100 or multiple PDUs registered. 101 102 Args: 103 device_pdu_config: a dict, representing the config of the device. 104 pdus: a list of registered PduDevice objects. 105 106 Returns: 107 A tuple: (PduObject for the device, string port number on that PDU). 108 109 Raises: 110 ValueError, if there is no PDU matching the given host in the config. 111 112 Example ACTS config: 113 ... 114 "testbed": [ 115 ... 116 "FuchsiaDevice": [ 117 { 118 "ip": "<device_ip>", 119 "ssh_config": "/path/to/sshconfig", 120 "PduDevice": { 121 "host": "192.168.42.185", 122 "port": 2 123 } 124 } 125 ], 126 "AccessPoint": [ 127 { 128 "ssh_config": { 129 ... 130 }, 131 "PduDevice": { 132 "host": "192.168.42.185", 133 "port" 1 134 } 135 } 136 ], 137 "PduDevice": [ 138 { 139 "device": "synaccess.np02b", 140 "host": "192.168.42.185" 141 } 142 ] 143 ], 144 ... 145 """ 146 pdu_ip = device_pdu_config['host'] 147 port = device_pdu_config['port'] 148 for pdu in pdus: 149 if pdu.host == pdu_ip: 150 return pdu, port 151 raise ValueError('No PduDevice with host: %s' % pdu_ip) 152 153 154class PduDevice(object): 155 """An object that defines the basic Pdu functionality and abstracts 156 the actual hardware. 157 158 This is a pure abstract class. Implementations should be of the same 159 class name (eg. class PduDevice(pdu.PduDevice)) and exist in 160 pdu_lib/<brand>/<device_name>.py. PduDevice objects should not be 161 instantiated by users directly. 162 """ 163 def __init__(self, host, username, password): 164 if type(self) is PduDevice: 165 raise NotImplementedError( 166 "Base class: cannot be instantiated directly") 167 self.host = host 168 self.username = username 169 self.password = password 170 self.log = tracelogger.TraceLogger(logging.getLogger()) 171 172 def on_all(self): 173 """Turns on all outlets on the device.""" 174 raise NotImplementedError("Base class: cannot be called directly") 175 176 def off_all(self): 177 """Turns off all outlets on the device.""" 178 raise NotImplementedError("Base class: cannot be called directly") 179 180 def on(self, outlet): 181 """Turns on specific outlet on the device. 182 Args: 183 outlet: a string of the outlet to turn on. 184 """ 185 raise NotImplementedError("Base class: cannot be called directly") 186 187 def off(self, outlet): 188 """Turns off specific outlet on the device. 189 Args: 190 outlet: a string of the outlet to turn off. 191 """ 192 raise NotImplementedError("Base class: cannot be called directly") 193 194 def reboot(self, outlet): 195 """Toggles a specific outlet on the device to off, then to on. 196 Args: 197 outlet: a string of the outlet to reboot. 198 """ 199 raise NotImplementedError("Base class: cannot be called directly") 200 201 def status(self): 202 """Retrieves the status of the outlets on the device. 203 204 Return: 205 A dictionary matching outlet string to: 206 True: if outlet is On 207 False: if outlet is Off 208 """ 209 raise NotImplementedError("Base class: cannot be called directly") 210 211 def close(self): 212 """Closes connection to the device.""" 213 raise NotImplementedError("Base class: cannot be called directly") 214 215 216class PduError(Exception): 217 """An exception for use within PduDevice implementations""" 218 pass