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 """This module provides utility functions to help managing servers in server 6 database (defined in global config section AUTOTEST_SERVER_DB). 7 8 """ 9 10 import collections 11 import json 12 import socket 13 import subprocess 14 import sys 15 16 import common 17 18 import django.core.exceptions 19 from autotest_lib.client.common_lib import utils 20 from autotest_lib.client.common_lib.global_config import global_config 21 from autotest_lib.frontend.server import models as server_models 22 from autotest_lib.site_utils.lib import infra 23 24 25 class ServerActionError(Exception): 26 """Exception raised when action on server failed. 27 """ 28 29 30 def use_server_db(): 31 """Check if use_server_db is enabled in configuration. 32 33 @return: True if use_server_db is set to True in global config. 34 """ 35 return global_config.get_config_value( 36 'SERVER', 'use_server_db', default=False, type=bool) 37 38 39 def warn_missing_role(role, exclude_server): 40 """Post a warning if Autotest instance has no other primary server with 41 given role. 42 43 @param role: Name of the role. 44 @param exclude_server: Server to be excluded from search for role. 45 """ 46 servers = server_models.Server.objects.filter( 47 roles__role=role, 48 status=server_models.Server.STATUS.PRIMARY).exclude( 49 hostname=exclude_server.hostname) 50 if not servers: 51 message = ('WARNING! There will be no server with role %s after it\'s ' 52 'removed from server %s. Autotest will not function ' 53 'normally without any server in role %s.' % 54 (role, exclude_server.hostname, role)) 55 print >> sys.stderr, message 56 57 58 def get_servers(hostname=None, role=None, status=None): 59 """Find servers with given role and status. 60 61 @param hostname: hostname of the server. 62 @param role: Role of server, default to None. 63 @param status: Status of server, default to None. 64 65 @return: A list of server objects with given role and status. 66 """ 67 filters = {} 68 if hostname: 69 filters['hostname'] = hostname 70 if role: 71 filters['roles__role'] = role 72 if status: 73 filters['status'] = status 74 return list(server_models.Server.objects.filter(**filters)) 75 76 77 def format_servers(servers): 78 """Format servers for printing. 79 80 Example output: 81 82 Hostname : server2 83 Status : primary 84 Roles : drone 85 Attributes : {'max_processes':300} 86 Date Created : 2014-11-25 12:00:00 87 Date Modified: None 88 Note : Drone in lab1 89 90 @param servers: Sequence of Server instances. 91 @returns: Formatted output as string. 92 """ 93 return '\n'.join(str(server) for server in servers) 94 95 96 def format_servers_json(servers): 97 """Format servers for printing as JSON. 98 99 Example output: 100 101 Hostname : server2 102 Status : primary 103 Roles : drone 104 Attributes : {'max_processes':300} 105 Date Created : 2014-11-25 12:00:00 106 Date Modified: None 107 Note : Drone in lab1 108 109 @param servers: Sequence of Server instances. 110 @returns: String. 111 """ 112 server_dicts = [] 113 for server in servers: 114 if server.date_modified is None: 115 date_modified = None 116 else: 117 date_modified = str(server.date_modified) 118 server_dicts.append({'hostname': server.hostname, 119 'status': server.status, 120 'roles': server.get_role_names(), 121 'date_created': str(server.date_created), 122 'date_modified': date_modified, 123 'note': server.note}) 124 return json.dumps(server_dicts) 125 126 127 _SERVER_TABLE_FORMAT = ('%(hostname)-30s | %(status)-7s | %(roles)-20s |' 128 ' %(date_created)-19s | %(date_modified)-19s |' 129 ' %(note)s') 130 131 132 def format_servers_table(servers): 133 """format servers for printing as a table. 134 135 Example output: 136 137 Hostname | Status | Roles | Date Created | Date Modified | Note 138 server1 | backup | scheduler | 2014-11-25 23:45:19 | | 139 server2 | primary | drone | 2014-11-25 12:00:00 | | Drone 140 141 @param servers: Sequence of Server instances. 142 @returns: Formatted output as string. 143 """ 144 result_lines = [(_SERVER_TABLE_FORMAT % 145 {'hostname': 'Hostname', 146 'status': 'Status', 147 'roles': 'Roles', 148 'date_created': 'Date Created', 149 'date_modified': 'Date Modified', 150 'note': 'Note'})] 151 for server in servers: 152 roles = ','.join(server.get_role_names()) 153 result_lines.append(_SERVER_TABLE_FORMAT % 154 {'hostname':server.hostname, 155 'status': server.status or '', 156 'roles': roles, 157 'date_created': server.date_created, 158 'date_modified': server.date_modified or '', 159 'note': server.note or ''}) 160 return '\n'.join(result_lines) 161 162 163 def format_servers_summary(servers): 164 """format servers for printing a summary. 165 166 Example output: 167 168 scheduler : server1(backup), server3(primary), 169 host_scheduler : 170 drone : server2(primary), 171 devserver : 172 database : 173 suite_scheduler: 174 crash_server : 175 No Role : 176 177 @param servers: Sequence of Server instances. 178 @returns: Formatted output as string. 179 """ 180 servers_by_role = _get_servers_by_role(servers) 181 servers_with_roles = {server for role_servers in servers_by_role.itervalues() 182 for server in role_servers} 183 servers_without_roles = [server for server in servers 184 if server not in servers_with_roles] 185 result_lines = ['Roles and status of servers:', ''] 186 for role, role_servers in servers_by_role.iteritems(): 187 result_lines.append(_format_role_servers_summary(role, role_servers)) 188 if servers_without_roles: 189 result_lines.append( 190 _format_role_servers_summary('No Role', servers_without_roles)) 191 return '\n'.join(result_lines) 192 193 194 def format_servers_nameonly(servers): 195 """format servers for printing names only 196 197 @param servers: Sequence of Server instances. 198 @returns: Formatted output as string. 199 """ 200 return '\n'.join(s.hostname for s in servers) 201 202 203 def _get_servers_by_role(servers): 204 """Return a mapping from roles to servers. 205 206 @param servers: Iterable of servers. 207 @returns: Mapping of role strings to lists of servers. 208 """ 209 roles = [role for role, _ in server_models.ServerRole.ROLE.choices()] 210 servers_by_role = collections.defaultdict(list) 211 for server in servers: 212 for role in server.get_role_names(): 213 servers_by_role[role].append(server) 214 return servers_by_role 215 216 217 def _format_role_servers_summary(role, servers): 218 """Format one line of servers for a role in a server list summary. 219 220 @param role: Role string. 221 @param servers: Iterable of Server instances. 222 @returns: String. 223 """ 224 servers_part = ', '.join( 225 '%s(%s)' % (server.hostname, server.status) 226 for server in servers) 227 return '%-15s: %s' % (role, servers_part) 228 229 230 def check_server(hostname, role): 231 """Confirm server with given hostname is ready to be primary of given role. 232 233 If the server is a backup and failed to be verified for the role, remove 234 the role from its roles list. If it has no other role, set its status to 235 repair_required. 236 237 @param hostname: hostname of the server. 238 @param role: Role to be checked. 239 @return: True if server can be verified for the given role, otherwise 240 return False. 241 """ 242 # TODO(dshi): Add more logic to confirm server is ready for the role. 243 # For now, the function just checks if server is ssh-able. 244 try: 245 infra.execute_command(hostname, 'true') 246 return True 247 except subprocess.CalledProcessError as e: 248 print >> sys.stderr, ('Failed to check server %s, error: %s' % 249 (hostname, e)) 250 return False 251 252 253 def verify_server(exist=True): 254 """Decorator to check if server with given hostname exists in the database. 255 256 @param exist: Set to True to confirm server exists in the database, raise 257 exception if not. If it's set to False, raise exception if 258 server exists in database. Default is True. 259 260 @raise ServerActionError: If `exist` is True and server does not exist in 261 the database, or `exist` is False and server exists 262 in the database. 263 """ 264 def deco_verify(func): 265 """Wrapper for the decorator. 266 267 @param func: Function to be called. 268 """ 269 def func_verify(*args, **kwargs): 270 """Decorator to check if server exists. 271 272 If exist is set to True, raise ServerActionError is server with 273 given hostname is not found in server database. 274 If exist is set to False, raise ServerActionError is server with 275 given hostname is found in server database. 276 277 @param func: function to be called. 278 @param args: arguments for function to be called. 279 @param kwargs: keyword arguments for function to be called. 280 """ 281 hostname = kwargs['hostname'] 282 try: 283 server = server_models.Server.objects.get(hostname=hostname) 284 except django.core.exceptions.ObjectDoesNotExist: 285 server = None 286 287 if not exist and server: 288 raise ServerActionError('Server %s already exists.' % 289 hostname) 290 if exist and not server: 291 raise ServerActionError('Server %s does not exist in the ' 292 'database.' % hostname) 293 if server: 294 kwargs['server'] = server 295 return func(*args, **kwargs) 296 return func_verify 297 return deco_verify 298 299 300 def get_drones(): 301 """Get a list of drones in status primary. 302 303 @return: A list of drones in status primary. 304 """ 305 servers = get_servers(role=server_models.ServerRole.ROLE.DRONE, 306 status=server_models.Server.STATUS.PRIMARY) 307 return [s.hostname for s in servers] 308 309 310 def delete_attribute(server, attribute): 311 """Delete the attribute from the host. 312 313 @param server: An object of server_models.Server. 314 @param attribute: Name of an attribute of the server. 315 """ 316 attributes = server.attributes.filter(attribute=attribute) 317 if not attributes: 318 raise ServerActionError('Server %s does not have attribute %s' % 319 (server.hostname, attribute)) 320 attributes[0].delete() 321 print 'Attribute %s is deleted from server %s.' % (attribute, 322 server.hostname) 323 324 325 def change_attribute(server, attribute, value): 326 """Change the value of an attribute of the server. 327 328 @param server: An object of server_models.Server. 329 @param attribute: Name of an attribute of the server. 330 @param value: Value of the attribute of the server. 331 332 @raise ServerActionError: If the attribute already exists and has the 333 given value. 334 """ 335 attributes = server_models.ServerAttribute.objects.filter( 336 server=server, attribute=attribute) 337 if attributes and attributes[0].value == value: 338 raise ServerActionError('Attribute %s for Server %s already has ' 339 'value of %s.' % 340 (attribute, server.hostname, value)) 341 if attributes: 342 old_value = attributes[0].value 343 attributes[0].value = value 344 attributes[0].save() 345 print ('Attribute `%s` of server %s is changed from %s to %s.' % 346 (attribute, server.hostname, old_value, value)) 347 else: 348 server_models.ServerAttribute.objects.create( 349 server=server, attribute=attribute, value=value) 350 print ('Attribute `%s` of server %s is set to %s.' % 351 (attribute, server.hostname, value)) 352 353 354 def get_shards(): 355 """Get a list of shards in status primary. 356 357 @return: A list of shards in status primary. 358 """ 359 servers = get_servers(role=server_models.ServerRole.ROLE.SHARD, 360 status=server_models.Server.STATUS.PRIMARY) 361 return [s.hostname for s in servers] 362 363 364 def confirm_server_has_role(hostname, role): 365 """Confirm a given server has the given role, and its status is primary. 366 367 @param hostname: hostname of the server. 368 @param role: Name of the role to be checked. 369 @raise ServerActionError: If localhost does not have given role or it's 370 not in primary status. 371 """ 372 if hostname.lower() in ['localhost', '127.0.0.1']: 373 hostname = socket.gethostname() 374 hostname = utils.normalize_hostname(hostname) 375 376 servers = get_servers(role=role, status=server_models.Server.STATUS.PRIMARY) 377 for server in servers: 378 if hostname == utils.normalize_hostname(server.hostname): 379 return True 380 raise ServerActionError('Server %s does not have role of %s running in ' 381 'status primary.' % (hostname, role)) 382