383 lines
15 KiB
Python
383 lines
15 KiB
Python
# Copyright 2014 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""
|
|
The server module contains the objects and methods used to manage servers in
|
|
Autotest.
|
|
|
|
The valid actions are:
|
|
list: list all servers in the database
|
|
create: create a server
|
|
delete: deletes a server
|
|
modify: modify a server's role or status.
|
|
|
|
The common options are:
|
|
--role / -r: role that's related to server actions.
|
|
|
|
See topic_common.py for a High Level Design and Algorithm.
|
|
"""
|
|
|
|
import common
|
|
|
|
from autotest_lib.cli import action_common
|
|
from autotest_lib.cli import topic_common
|
|
from autotest_lib.client.common_lib import error
|
|
# The django setup is moved here as test_that uses sqlite setup. If this line
|
|
# is in server_manager, test_that unittest will fail.
|
|
from autotest_lib.frontend import setup_django_environment
|
|
from autotest_lib.site_utils import server_manager
|
|
from autotest_lib.site_utils import server_manager_utils
|
|
|
|
|
|
class server(topic_common.atest):
|
|
"""Server class
|
|
|
|
atest server [list|create|delete|modify] <options>
|
|
"""
|
|
usage_action = '[list|create|delete|modify]'
|
|
topic = msg_topic = 'server'
|
|
msg_items = '<server>'
|
|
|
|
def __init__(self, hostname_required=True):
|
|
"""Add to the parser the options common to all the server actions.
|
|
|
|
@param hostname_required: True to require the command has hostname
|
|
specified. Default is True.
|
|
"""
|
|
super(server, self).__init__()
|
|
|
|
self.parser.add_option('-r', '--role',
|
|
help='Name of a role',
|
|
type='string',
|
|
default=None,
|
|
metavar='ROLE')
|
|
self.parser.add_option('-x', '--action',
|
|
help=('Set to True to apply actions when role '
|
|
'or status is changed, e.g., restart '
|
|
'scheduler when a drone is removed.'),
|
|
action='store_true',
|
|
default=False,
|
|
metavar='ACTION')
|
|
|
|
self.topic_parse_info = topic_common.item_parse_info(
|
|
attribute_name='hostname', use_leftover=True)
|
|
|
|
self.hostname_required = hostname_required
|
|
|
|
|
|
def parse(self):
|
|
"""Parse command arguments.
|
|
"""
|
|
role_info = topic_common.item_parse_info(attribute_name='role')
|
|
kwargs = {}
|
|
if self.hostname_required:
|
|
kwargs['req_items'] = 'hostname'
|
|
(options, leftover) = super(server, self).parse([role_info], **kwargs)
|
|
if options.web_server:
|
|
self.invalid_syntax('Server actions will access server database '
|
|
'defined in your local global config. It does '
|
|
'not rely on RPC, no autotest server needs to '
|
|
'be specified.')
|
|
|
|
# self.hostname is a list. Action on server only needs one hostname at
|
|
# most.
|
|
if ((not self.hostname and self.hostname_required) or
|
|
len(self.hostname) > 1):
|
|
self.invalid_syntax('`server` topic can only manipulate 1 server. '
|
|
'Use -h to see available options.')
|
|
if self.hostname:
|
|
# Override self.hostname with the first hostname in the list.
|
|
self.hostname = self.hostname[0]
|
|
self.role = options.role
|
|
return (options, leftover)
|
|
|
|
|
|
def output(self, results):
|
|
"""Display output.
|
|
|
|
For most actions, the return is a string message, no formating needed.
|
|
|
|
@param results: return of the execute call.
|
|
"""
|
|
print results
|
|
|
|
|
|
class server_help(server):
|
|
"""Just here to get the atest logic working. Usage is set by its parent.
|
|
"""
|
|
pass
|
|
|
|
|
|
class server_list(action_common.atest_list, server):
|
|
"""atest server list [--role <role>]"""
|
|
|
|
def __init__(self):
|
|
"""Initializer.
|
|
"""
|
|
super(server_list, self).__init__(hostname_required=False)
|
|
self.parser.add_option('-t', '--table',
|
|
help=('List details of all servers in a table, '
|
|
'e.g., \tHostname | Status | Roles | '
|
|
'note\t\tserver1 | primary | scheduler | '
|
|
'lab'),
|
|
action='store_true',
|
|
default=False)
|
|
self.parser.add_option('-s', '--status',
|
|
help='Only show servers with given status',
|
|
type='string',
|
|
default=None,
|
|
metavar='STATUS')
|
|
self.parser.add_option('-u', '--summary',
|
|
help=('Show the summary of roles and status '
|
|
'only, e.g.,\tscheduler: server1(primary) '
|
|
'server2(backup)\t\tdrone: server3(primary'
|
|
') server4(backup)'),
|
|
action='store_true',
|
|
default=False)
|
|
self.parser.add_option('--json',
|
|
help='Format output as JSON.',
|
|
action='store_true',
|
|
default=False)
|
|
self.parser.add_option('-N', '--hostnames-only',
|
|
help='Only return hostnames.',
|
|
action='store_true',
|
|
default=False)
|
|
|
|
|
|
def parse(self):
|
|
"""Parse command arguments.
|
|
"""
|
|
(options, leftover) = super(server_list, self).parse()
|
|
self.json = options.json
|
|
self.table = options.table
|
|
self.status = options.status
|
|
self.summary = options.summary
|
|
self.namesonly = options.hostnames_only
|
|
if sum([self.table, self.summary, self.json, self.namesonly]) > 1:
|
|
self.invalid_syntax('May only specify up to 1 output-format flag.')
|
|
return (options, leftover)
|
|
|
|
|
|
def execute(self):
|
|
"""Execute the command.
|
|
|
|
@return: A list of servers matched given hostname and role.
|
|
"""
|
|
try:
|
|
return server_manager_utils.get_servers(hostname=self.hostname,
|
|
role=self.role,
|
|
status=self.status)
|
|
except (server_manager_utils.ServerActionError,
|
|
error.InvalidDataError) as e:
|
|
self.failure(e, what_failed='Failed to find servers',
|
|
item=self.hostname, fatal=True)
|
|
|
|
|
|
def output(self, results):
|
|
"""Display output.
|
|
|
|
@param results: return of the execute call, a list of server object that
|
|
contains server information.
|
|
"""
|
|
if results:
|
|
if self.json:
|
|
formatter = server_manager_utils.format_servers_json
|
|
elif self.table:
|
|
formatter = server_manager_utils.format_servers_table
|
|
elif self.summary:
|
|
formatter = server_manager_utils.format_servers_summary
|
|
elif self.namesonly:
|
|
formatter = server_manager_utils.format_servers_nameonly
|
|
else:
|
|
formatter = server_manager_utils.format_servers
|
|
print formatter(results)
|
|
else:
|
|
self.failure('No server is found.',
|
|
what_failed='Failed to find servers',
|
|
item=self.hostname, fatal=True)
|
|
|
|
|
|
class server_create(server):
|
|
"""atest server create hostname --role <role> --note <note>
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initializer.
|
|
"""
|
|
super(server_create, self).__init__()
|
|
self.parser.add_option('-n', '--note',
|
|
help='note of the server',
|
|
type='string',
|
|
default=None,
|
|
metavar='NOTE')
|
|
|
|
|
|
def parse(self):
|
|
"""Parse command arguments.
|
|
"""
|
|
(options, leftover) = super(server_create, self).parse()
|
|
self.note = options.note
|
|
|
|
if not self.role:
|
|
self.invalid_syntax('--role is required to create a server.')
|
|
|
|
return (options, leftover)
|
|
|
|
|
|
def execute(self):
|
|
"""Execute the command.
|
|
|
|
@return: A Server object if it is created successfully.
|
|
"""
|
|
try:
|
|
return server_manager.create(hostname=self.hostname, role=self.role,
|
|
note=self.note)
|
|
except (server_manager_utils.ServerActionError,
|
|
error.InvalidDataError) as e:
|
|
self.failure(e, what_failed='Failed to create server',
|
|
item=self.hostname, fatal=True)
|
|
|
|
|
|
def output(self, results):
|
|
"""Display output.
|
|
|
|
@param results: return of the execute call, a server object that
|
|
contains server information.
|
|
"""
|
|
if results:
|
|
print 'Server %s is added to server database:\n' % self.hostname
|
|
print results
|
|
|
|
|
|
class server_delete(server):
|
|
"""atest server delete hostname"""
|
|
|
|
def execute(self):
|
|
"""Execute the command.
|
|
|
|
@return: True if server is deleted successfully.
|
|
"""
|
|
try:
|
|
server_manager.delete(hostname=self.hostname)
|
|
return True
|
|
except (server_manager_utils.ServerActionError,
|
|
error.InvalidDataError) as e:
|
|
self.failure(e, what_failed='Failed to delete server',
|
|
item=self.hostname, fatal=True)
|
|
|
|
|
|
def output(self, results):
|
|
"""Display output.
|
|
|
|
@param results: return of the execute call.
|
|
"""
|
|
if results:
|
|
print ('Server %s is deleted from server database successfully.' %
|
|
self.hostname)
|
|
|
|
|
|
class server_modify(server):
|
|
"""atest server modify hostname
|
|
|
|
modify action can only change one input at a time. Available inputs are:
|
|
--status: Status of the server.
|
|
--note: Note of the server.
|
|
--role: New role to be added to the server.
|
|
--delete_role: Existing role to be deleted from the server.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initializer.
|
|
"""
|
|
super(server_modify, self).__init__()
|
|
self.parser.add_option('-s', '--status',
|
|
help='Status of the server',
|
|
type='string',
|
|
metavar='STATUS')
|
|
self.parser.add_option('-n', '--note',
|
|
help='Note of the server',
|
|
type='string',
|
|
default=None,
|
|
metavar='NOTE')
|
|
self.parser.add_option('-d', '--delete',
|
|
help=('Set to True to delete given role.'),
|
|
action='store_true',
|
|
default=False,
|
|
metavar='DELETE')
|
|
self.parser.add_option('-a', '--attribute',
|
|
help='Name of the attribute of the server',
|
|
type='string',
|
|
default=None,
|
|
metavar='ATTRIBUTE')
|
|
self.parser.add_option('-e', '--value',
|
|
help='Value for the attribute of the server',
|
|
type='string',
|
|
default=None,
|
|
metavar='VALUE')
|
|
|
|
|
|
def parse(self):
|
|
"""Parse command arguments.
|
|
"""
|
|
(options, leftover) = super(server_modify, self).parse()
|
|
self.status = options.status
|
|
self.note = options.note
|
|
self.delete = options.delete
|
|
self.attribute = options.attribute
|
|
self.value = options.value
|
|
self.action = options.action
|
|
|
|
# modify supports various options. However, it's safer to limit one
|
|
# option at a time so no complicated role-dependent logic is needed
|
|
# to handle scenario that both role and status are changed.
|
|
# self.parser is optparse, which does not have function in argparse like
|
|
# add_mutually_exclusive_group. That's why the count is used here.
|
|
flags = [self.status is not None, self.role is not None,
|
|
self.attribute is not None, self.note is not None]
|
|
if flags.count(True) != 1:
|
|
msg = ('Action modify only support one option at a time. You can '
|
|
'try one of following 5 options:\n'
|
|
'1. --status: Change server\'s status.\n'
|
|
'2. --note: Change server\'s note.\n'
|
|
'3. --role with optional -d: Add/delete role from server.\n'
|
|
'4. --attribute --value: Set/change the value of a '
|
|
'server\'s attribute.\n'
|
|
'5. --attribute -d: Delete the attribute from the '
|
|
'server.\n'
|
|
'\nUse option -h to see a complete list of options.')
|
|
self.invalid_syntax(msg)
|
|
if (self.status != None or self.note != None) and self.delete:
|
|
self.invalid_syntax('--delete does not apply to status or note.')
|
|
if self.attribute != None and not self.delete and self.value == None:
|
|
self.invalid_syntax('--attribute must be used with option --value '
|
|
'or --delete.')
|
|
return (options, leftover)
|
|
|
|
|
|
def execute(self):
|
|
"""Execute the command.
|
|
|
|
@return: The updated server object if it is modified successfully.
|
|
"""
|
|
try:
|
|
return server_manager.modify(hostname=self.hostname, role=self.role,
|
|
status=self.status, delete=self.delete,
|
|
note=self.note,
|
|
attribute=self.attribute,
|
|
value=self.value, action=self.action)
|
|
except (server_manager_utils.ServerActionError,
|
|
error.InvalidDataError) as e:
|
|
self.failure(e, what_failed='Failed to modify server',
|
|
item=self.hostname, fatal=True)
|
|
|
|
|
|
def output(self, results):
|
|
"""Display output.
|
|
|
|
@param results: return of the execute call, which is the updated server
|
|
object.
|
|
"""
|
|
if results:
|
|
print 'Server %s is modified successfully.' % self.hostname
|
|
print results
|