blob: e69d6f27dbf89a593a262b9d372ba48bf67a073d [file] [log] [blame]
Dan Shi56f1ba72014-12-03 19:16:53 -08001# 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
6database (defined in global config section AUTOTEST_SERVER_DB).
7
8"""
9
Allen Li90a84ea2016-10-27 15:07:42 -070010import collections
Allen Lica17e7c2016-10-27 15:37:17 -070011import json
Dan Shib9144a42014-12-01 16:09:32 -080012import socket
Dan Shi56f1ba72014-12-03 19:16:53 -080013import subprocess
14import sys
15
16import common
17
18import django.core.exceptions
Dan Shib9144a42014-12-01 16:09:32 -080019from autotest_lib.client.common_lib import base_utils as utils
Dan Shi56f1ba72014-12-03 19:16:53 -080020from autotest_lib.client.common_lib.global_config import global_config
21from autotest_lib.frontend.server import models as server_models
22from autotest_lib.site_utils.lib import infra
23
24
25class ServerActionError(Exception):
26 """Exception raised when action on server failed.
27 """
28
29
30def 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
39def 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
58def 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
Allen Li90a84ea2016-10-27 15:07:42 -070077def format_servers(servers):
78 """Format servers for printing.
Dan Shi56f1ba72014-12-03 19:16:53 -080079
Allen Li90a84ea2016-10-27 15:07:42 -070080 Example output:
81
Dan Shi56f1ba72014-12-03 19:16:53 -080082 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
Allen Li90a84ea2016-10-27 15:07:42 -070089
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
Allen Lica17e7c2016-10-27 15:37:17 -070096def 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
Allen Li90a84ea2016-10-27 15:07:42 -0700127_SERVER_TABLE_FORMAT = ('%(hostname)-30s | %(status)-7s | %(roles)-20s |'
128 ' %(date_created)-19s | %(date_modified)-19s |'
129 ' %(note)s')
130
131
132def format_servers_table(servers):
133 """format servers for printing as a table.
134
135 Example output:
136
Dan Shi56f1ba72014-12-03 19:16:53 -0800137 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
Allen Li90a84ea2016-10-27 15:07:42 -0700140
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
163def format_servers_summary(servers):
164 """format servers for printing a summary.
165
166 Example output:
167
Dan Shi56f1ba72014-12-03 19:16:53 -0800168 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
Allen Li90a84ea2016-10-27 15:07:42 -0700177 @param servers: Sequence of Server instances.
178 @returns: Formatted output as string.
Dan Shi56f1ba72014-12-03 19:16:53 -0800179 """
Allen Li90a84ea2016-10-27 15:07:42 -0700180 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)
Dan Shi56f1ba72014-12-03 19:16:53 -0800192
Dan Shi56f1ba72014-12-03 19:16:53 -0800193
Allen Li90a84ea2016-10-27 15:07:42 -0700194def _get_servers_by_role(servers):
195 """Return a mapping from roles to servers.
196
197 @param servers: Iterable of servers.
198 @returns: Mapping of role strings to lists of servers.
199 """
200 roles = [role for role, _ in server_models.ServerRole.ROLE.choices()]
201 servers_by_role = collections.defaultdict(list)
202 for server in servers:
203 for role in server.get_role_names():
204 servers_by_role[role].append(server)
205 return servers_by_role
206
207
208def _format_role_servers_summary(role, servers):
209 """Format one line of servers for a role in a server list summary.
210
211 @param role: Role string.
212 @param servers: Iterable of Server instances.
213 @returns: String.
214 """
215 servers_part = ', '.join(
216 '%s(%s)' % (server.hostname, server.status)
217 for server in servers)
218 return '%-15s: %s' % (role, servers_part)
Dan Shi56f1ba72014-12-03 19:16:53 -0800219
220
221def check_server(hostname, role):
222 """Confirm server with given hostname is ready to be primary of given role.
223
224 If the server is a backup and failed to be verified for the role, remove
225 the role from its roles list. If it has no other role, set its status to
226 repair_required.
227
228 @param hostname: hostname of the server.
229 @param role: Role to be checked.
230 @return: True if server can be verified for the given role, otherwise
231 return False.
232 """
233 # TODO(dshi): Add more logic to confirm server is ready for the role.
234 # For now, the function just checks if server is ssh-able.
235 try:
236 infra.execute_command(hostname, 'true')
237 return True
238 except subprocess.CalledProcessError as e:
239 print >> sys.stderr, ('Failed to check server %s, error: %s' %
240 (hostname, e))
241 return False
242
243
244def verify_server(exist=True):
245 """Decorator to check if server with given hostname exists in the database.
246
247 @param exist: Set to True to confirm server exists in the database, raise
248 exception if not. If it's set to False, raise exception if
249 server exists in database. Default is True.
250
251 @raise ServerActionError: If `exist` is True and server does not exist in
252 the database, or `exist` is False and server exists
253 in the database.
254 """
255 def deco_verify(func):
256 """Wrapper for the decorator.
257
258 @param func: Function to be called.
259 """
260 def func_verify(*args, **kwargs):
261 """Decorator to check if server exists.
262
263 If exist is set to True, raise ServerActionError is server with
264 given hostname is not found in server database.
265 If exist is set to False, raise ServerActionError is server with
266 given hostname is found in server database.
267
268 @param func: function to be called.
269 @param args: arguments for function to be called.
270 @param kwargs: keyword arguments for function to be called.
271 """
272 hostname = kwargs['hostname']
273 try:
274 server = server_models.Server.objects.get(hostname=hostname)
275 except django.core.exceptions.ObjectDoesNotExist:
276 server = None
277
278 if not exist and server:
279 raise ServerActionError('Server %s already exists.' %
280 hostname)
281 if exist and not server:
282 raise ServerActionError('Server %s does not exist in the '
283 'database.' % hostname)
284 if server:
285 kwargs['server'] = server
286 return func(*args, **kwargs)
287 return func_verify
288 return deco_verify
289
290
291def get_drones():
292 """Get a list of drones in status primary.
293
294 @return: A list of drones in status primary.
295 """
296 servers = get_servers(role=server_models.ServerRole.ROLE.DRONE,
297 status=server_models.Server.STATUS.PRIMARY)
298 return [s.hostname for s in servers]
299
300
301def delete_attribute(server, attribute):
302 """Delete the attribute from the host.
303
304 @param server: An object of server_models.Server.
305 @param attribute: Name of an attribute of the server.
306 """
307 attributes = server.attributes.filter(attribute=attribute)
308 if not attributes:
309 raise ServerActionError('Server %s does not have attribute %s' %
310 (server.hostname, attribute))
311 attributes[0].delete()
312 print 'Attribute %s is deleted from server %s.' % (attribute,
313 server.hostname)
314
315
316def change_attribute(server, attribute, value):
317 """Change the value of an attribute of the server.
318
319 @param server: An object of server_models.Server.
320 @param attribute: Name of an attribute of the server.
321 @param value: Value of the attribute of the server.
322
323 @raise ServerActionError: If the attribute already exists and has the
324 given value.
325 """
326 attributes = server_models.ServerAttribute.objects.filter(
327 server=server, attribute=attribute)
328 if attributes and attributes[0].value == value:
329 raise ServerActionError('Attribute %s for Server %s already has '
330 'value of %s.' %
331 (attribute, server.hostname, value))
332 if attributes:
333 old_value = attributes[0].value
334 attributes[0].value = value
335 attributes[0].save()
336 print ('Attribute `%s` of server %s is changed from %s to %s.' %
337 (attribute, server.hostname, old_value, value))
338 else:
339 server_models.ServerAttribute.objects.create(
340 server=server, attribute=attribute, value=value)
341 print ('Attribute `%s` of server %s is set to %s.' %
342 (attribute, server.hostname, value))
Dan Shib9144a42014-12-01 16:09:32 -0800343
344
MK Ryua50e70e2015-07-14 11:34:25 -0700345def get_shards():
346 """Get a list of shards in status primary.
347
348 @return: A list of shards in status primary.
349 """
350 servers = get_servers(role=server_models.ServerRole.ROLE.SHARD,
351 status=server_models.Server.STATUS.PRIMARY)
352 return [s.hostname for s in servers]
353
354
Dan Shib9144a42014-12-01 16:09:32 -0800355def confirm_server_has_role(hostname, role):
356 """Confirm a given server has the given role, and its status is primary.
357
358 @param hostname: hostname of the server.
359 @param role: Name of the role to be checked.
360 @raise ServerActionError: If localhost does not have given role or it's
361 not in primary status.
362 """
363 if hostname.lower() in ['localhost', '127.0.0.1']:
364 hostname = socket.gethostname()
365 hostname = utils.normalize_hostname(hostname)
366
367 servers = get_servers(role=role, status=server_models.Server.STATUS.PRIMARY)
368 for server in servers:
369 if hostname == utils.normalize_hostname(server.hostname):
370 return True
371 raise ServerActionError('Server %s does not have role of %s running in '
372 'status primary.' % (hostname, role))