1# Copyright 2014 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5""" 6The server module contains the objects and methods used to manage servers in 7Autotest. 8 9The valid actions are: 10list: list all servers in the database 11create: create a server 12delete: deletes a server 13modify: modify a server's role or status. 14 15The common options are: 16--role / -r: role that's related to server actions. 17 18See topic_common.py for a High Level Design and Algorithm. 19""" 20 21import common 22 23from autotest_lib.cli import action_common 24from autotest_lib.cli import topic_common 25from autotest_lib.client.common_lib import error 26# The django setup is moved here as test_that uses sqlite setup. If this line 27# is in server_manager, test_that unittest will fail. 28from autotest_lib.frontend import setup_django_environment 29from autotest_lib.site_utils import server_manager 30from autotest_lib.site_utils import server_manager_utils 31 32 33class server(topic_common.atest): 34 """Server class 35 36 atest server [list|create|delete|modify] <options> 37 """ 38 usage_action = '[list|create|delete|modify]' 39 topic = msg_topic = 'server' 40 msg_items = '<server>' 41 42 def __init__(self, hostname_required=True): 43 """Add to the parser the options common to all the server actions. 44 45 @param hostname_required: True to require the command has hostname 46 specified. Default is True. 47 """ 48 super(server, self).__init__() 49 50 self.parser.add_option('-r', '--role', 51 help='Name of a role', 52 type='string', 53 default=None, 54 metavar='ROLE') 55 self.parser.add_option('-x', '--action', 56 help=('Set to True to apply actions when role ' 57 'or status is changed, e.g., restart ' 58 'scheduler when a drone is removed.'), 59 action='store_true', 60 default=False, 61 metavar='ACTION') 62 63 self.topic_parse_info = topic_common.item_parse_info( 64 attribute_name='hostname', use_leftover=True) 65 66 self.hostname_required = hostname_required 67 68 69 def parse(self): 70 """Parse command arguments. 71 """ 72 role_info = topic_common.item_parse_info(attribute_name='role') 73 kwargs = {} 74 if self.hostname_required: 75 kwargs['req_items'] = 'hostname' 76 (options, leftover) = super(server, self).parse([role_info], **kwargs) 77 if options.web_server: 78 self.invalid_syntax('Server actions will access server database ' 79 'defined in your local global config. It does ' 80 'not rely on RPC, no autotest server needs to ' 81 'be specified.') 82 83 # self.hostname is a list. Action on server only needs one hostname at 84 # most. 85 if ((not self.hostname and self.hostname_required) or 86 len(self.hostname) > 1): 87 self.invalid_syntax('`server` topic can only manipulate 1 server. ' 88 'Use -h to see available options.') 89 if self.hostname: 90 # Override self.hostname with the first hostname in the list. 91 self.hostname = self.hostname[0] 92 self.role = options.role 93 return (options, leftover) 94 95 96 def output(self, results): 97 """Display output. 98 99 For most actions, the return is a string message, no formating needed. 100 101 @param results: return of the execute call. 102 """ 103 print results 104 105 106class server_help(server): 107 """Just here to get the atest logic working. Usage is set by its parent. 108 """ 109 pass 110 111 112class server_list(action_common.atest_list, server): 113 """atest server list [--role <role>]""" 114 115 def __init__(self): 116 """Initializer. 117 """ 118 super(server_list, self).__init__(hostname_required=False) 119 self.parser.add_option('-t', '--table', 120 help=('List details of all servers in a table, ' 121 'e.g., \tHostname | Status | Roles | ' 122 'note\t\tserver1 | primary | scheduler | ' 123 'lab'), 124 action='store_true', 125 default=False, 126 metavar='TABLE') 127 self.parser.add_option('-s', '--status', 128 help='Only show servers with given status', 129 type='string', 130 default=None, 131 metavar='STATUS') 132 self.parser.add_option('-u', '--summary', 133 help=('Show the summary of roles and status ' 134 'only, e.g.,\tscheduler: server1(primary) ' 135 'server2(backup)\t\tdrone: server3(primary' 136 ') server4(backup)'), 137 action='store_true', 138 default=False, 139 metavar='SUMMARY') 140 141 142 def parse(self): 143 """Parse command arguments. 144 """ 145 (options, leftover) = super(server_list, self).parse() 146 self.table = options.table 147 self.status = options.status 148 self.summary = options.summary 149 if self.table and self.summary: 150 self.invalid_syntax('Option --table and --summary cannot be both ' 151 'specified.') 152 return (options, leftover) 153 154 155 def execute(self): 156 """Execute the command. 157 158 @return: A list of servers matched given hostname and role. 159 """ 160 try: 161 return server_manager_utils.get_servers(hostname=self.hostname, 162 role=self.role, 163 status=self.status) 164 except (server_manager_utils.ServerActionError, 165 error.InvalidDataError) as e: 166 self.failure(e, what_failed='Failed to find servers', 167 item=self.hostname, fatal=True) 168 169 170 def output(self, results): 171 """Display output. 172 173 @param results: return of the execute call, a list of server object that 174 contains server information. 175 """ 176 if not results: 177 self.failure('No server is found.', 178 what_failed='Failed to find servers', 179 item=self.hostname, fatal=True) 180 else: 181 print server_manager_utils.get_server_details(results, self.table, 182 self.summary) 183 184 185class server_create(server): 186 """atest server create hostname --role <role> --note <note> 187 """ 188 189 def __init__(self): 190 """Initializer. 191 """ 192 super(server_create, self).__init__() 193 self.parser.add_option('-n', '--note', 194 help='note of the server', 195 type='string', 196 default=None, 197 metavar='NOTE') 198 199 200 def parse(self): 201 """Parse command arguments. 202 """ 203 (options, leftover) = super(server_create, self).parse() 204 self.note = options.note 205 206 if not self.role: 207 self.invalid_syntax('--role is required to create a server.') 208 209 return (options, leftover) 210 211 212 def execute(self): 213 """Execute the command. 214 215 @return: A Server object if it is created successfully. 216 """ 217 try: 218 return server_manager.create(hostname=self.hostname, role=self.role, 219 note=self.note) 220 except (server_manager_utils.ServerActionError, 221 error.InvalidDataError) as e: 222 self.failure(e, what_failed='Failed to create server', 223 item=self.hostname, fatal=True) 224 225 226 def output(self, results): 227 """Display output. 228 229 @param results: return of the execute call, a server object that 230 contains server information. 231 """ 232 if results: 233 print 'Server %s is added to server database:\n' % self.hostname 234 print results 235 236 237class server_delete(server): 238 """atest server delete hostname""" 239 240 def execute(self): 241 """Execute the command. 242 243 @return: True if server is deleted successfully. 244 """ 245 try: 246 server_manager.delete(hostname=self.hostname) 247 return True 248 except (server_manager_utils.ServerActionError, 249 error.InvalidDataError) as e: 250 self.failure(e, what_failed='Failed to delete server', 251 item=self.hostname, fatal=True) 252 253 254 def output(self, results): 255 """Display output. 256 257 @param results: return of the execute call. 258 """ 259 if results: 260 print ('Server %s is deleted from server database successfully.' % 261 self.hostname) 262 263 264class server_modify(server): 265 """atest server modify hostname 266 267 modify action can only change one input at a time. Available inputs are: 268 --status: Status of the server. 269 --note: Note of the server. 270 --role: New role to be added to the server. 271 --delete_role: Existing role to be deleted from the server. 272 """ 273 274 def __init__(self): 275 """Initializer. 276 """ 277 super(server_modify, self).__init__() 278 self.parser.add_option('-s', '--status', 279 help='Status of the server', 280 type='string', 281 metavar='STATUS') 282 self.parser.add_option('-n', '--note', 283 help='Note of the server', 284 type='string', 285 default=None, 286 metavar='NOTE') 287 self.parser.add_option('-d', '--delete', 288 help=('Set to True to delete given role.'), 289 action='store_true', 290 default=False, 291 metavar='DELETE') 292 self.parser.add_option('-a', '--attribute', 293 help='Name of the attribute of the server', 294 type='string', 295 default=None, 296 metavar='ATTRIBUTE') 297 self.parser.add_option('-e', '--value', 298 help='Value for the attribute of the server', 299 type='string', 300 default=None, 301 metavar='VALUE') 302 303 304 def parse(self): 305 """Parse command arguments. 306 """ 307 (options, leftover) = super(server_modify, self).parse() 308 self.status = options.status 309 self.note = options.note 310 self.delete = options.delete 311 self.attribute = options.attribute 312 self.value = options.value 313 self.action = options.action 314 315 # modify supports various options. However, it's safer to limit one 316 # option at a time so no complicated role-dependent logic is needed 317 # to handle scenario that both role and status are changed. 318 # self.parser is optparse, which does not have function in argparse like 319 # add_mutually_exclusive_group. That's why the count is used here. 320 flags = [self.status is not None, self.role is not None, 321 self.attribute is not None, self.note is not None] 322 if flags.count(True) != 1: 323 msg = ('Action modify only support one option at a time. You can ' 324 'try one of following 5 options:\n' 325 '1. --status: Change server\'s status.\n' 326 '2. --note: Change server\'s note.\n' 327 '3. --role with optional -d: Add/delete role from server.\n' 328 '4. --attribute --value: Set/change the value of a ' 329 'server\'s attribute.\n' 330 '5. --attribute -d: Delete the attribute from the ' 331 'server.\n' 332 '\nUse option -h to see a complete list of options.') 333 self.invalid_syntax(msg) 334 if (self.status != None or self.note != None) and self.delete: 335 self.invalid_syntax('--delete does not apply to status or note.') 336 if self.attribute != None and not self.delete and self.value == None: 337 self.invalid_syntax('--attribute must be used with option --value ' 338 'or --delete.') 339 return (options, leftover) 340 341 342 def execute(self): 343 """Execute the command. 344 345 @return: The updated server object if it is modified successfully. 346 """ 347 try: 348 return server_manager.modify(hostname=self.hostname, role=self.role, 349 status=self.status, delete=self.delete, 350 note=self.note, 351 attribute=self.attribute, 352 value=self.value, action=self.action) 353 except (server_manager_utils.ServerActionError, 354 error.InvalidDataError) as e: 355 self.failure(e, what_failed='Failed to modify server', 356 item=self.hostname, fatal=True) 357 358 359 def output(self, results): 360 """Display output. 361 362 @param results: return of the execute call, which is the updated server 363 object. 364 """ 365 if results: 366 print 'Server %s is modified successfully.' % self.hostname 367 print results 368