blob: 10be603a27ddd0476036bc70e006a88f5d10dc84 [file] [log] [blame]
Dan Shi9a535c92014-11-24 08:52:40 -08001# Copyright (c) 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"""Django model for server database.
6"""
7
Dan Shi9a535c92014-11-24 08:52:40 -08008from django.db import models as dbmodels
9
10import common
Dan Shi9a535c92014-11-24 08:52:40 -080011from autotest_lib.client.common_lib import enum
12from autotest_lib.client.common_lib import error
Dan Shi0697ccd2015-01-05 12:07:38 -080013from autotest_lib.client.common_lib.cros.network import ping_runner
Dan Shi9a535c92014-11-24 08:52:40 -080014from autotest_lib.frontend.afe import model_logic
15
16
17class Server(dbmodels.Model, model_logic.ModelExtensions):
18 """Models a server."""
19 DETAIL_FMT = ('Hostname : %(hostname)s\n'
20 'Status : %(status)s\n'
21 'Roles : %(roles)s\n'
22 'Attributes : %(attributes)s\n'
23 'Date Created : %(date_created)s\n'
24 'Date Modified: %(date_modified)s\n'
25 'Note : %(note)s\n')
26
27 STATUS_LIST = ['primary', 'backup', 'repair_required']
28 STATUS = enum.Enum(*STATUS_LIST, string_values=True)
29
30 hostname = dbmodels.CharField(unique=True, max_length=128)
31 cname = dbmodels.CharField(null=True, blank=True, default=None,
32 max_length=128)
33 status = dbmodels.CharField(unique=False, max_length=128,
34 choices=STATUS.choices())
35 date_created = dbmodels.DateTimeField(null=True, blank=True)
36 date_modified = dbmodels.DateTimeField(null=True, blank=True)
37 note = dbmodels.TextField(null=True, blank=True)
38
39 objects = model_logic.ExtendedManager()
40
41 class Meta:
42 """Metadata for class Server."""
43 db_table = 'servers'
44
45
46 def __unicode__(self):
47 """A string representation of the Server object.
48 """
49 roles = ','.join([r.role for r in self.roles.all()])
50 attributes = dict([(a.attribute, a.value)
51 for a in self.attributes.all()])
52 return self.DETAIL_FMT % {'hostname': self.hostname,
53 'status': self.status,
54 'roles': roles,
55 'attributes': attributes,
56 'date_created': self.date_created,
57 'date_modified': self.date_modified,
58 'note': self.note}
59
60
Dan Shi56f1ba72014-12-03 19:16:53 -080061 def get_role_names(self):
62 """Get a list of role names of the server.
63
64 @return: A list of role names of the server.
65 """
66 return [r.role for r in self.roles.all()]
67
68
Dan Shid7bb4f12015-01-06 10:53:50 -080069 def get_details(self):
70 """Get a dictionary with all server details.
71
72 For example:
73 {
74 'hostname': 'server1',
75 'status': 'backup',
76 'roles': ['drone', 'scheduler'],
77 'attributes': {'max_processes': 300}
78 }
79
80 @return: A dictionary with all server details.
81 """
82 details = {}
83 details['hostname'] = self.hostname
84 details['status'] = self.status
85 details['roles'] = self.get_role_names()
86 attributes = dict([(a.attribute, a.value)
87 for a in self.attributes.all()])
88 details['attributes'] = attributes
89 details['date_created'] = self.date_created
90 details['date_modified'] = self.date_modified
91 details['note'] = self.note
92 return details
93
94
Dan Shi9a535c92014-11-24 08:52:40 -080095class ServerRole(dbmodels.Model, model_logic.ModelExtensions):
96 """Role associated with hosts."""
97 # Valid roles for a server.
98 ROLE_LIST = ['scheduler', 'host_scheduler', 'drone', 'devserver',
Dan Shibf0414d2015-01-05 10:56:02 -080099 'database', 'suite_scheduler', 'crash_server', 'shard',
100 'golo_proxy']
Dan Shi9a535c92014-11-24 08:52:40 -0800101 ROLE = enum.Enum(*ROLE_LIST, string_values=True)
102 # When deleting any of following roles from a primary server, a working
103 # backup must be available if user_server_db is enabled in global config.
104 ROLES_REQUIRE_BACKUP = [ROLE.SCHEDULER, ROLE.HOST_SCHEDULER,
105 ROLE.DATABASE, ROLE.SUITE_SCHEDULER,
106 ROLE.DRONE]
107 # Roles that must be assigned to a single primary server in an Autotest
108 # instance
109 ROLES_REQUIRE_UNIQUE_INSTANCE = [ROLE.SCHEDULER,
110 ROLE.HOST_SCHEDULER,
111 ROLE.DATABASE,
112 ROLE.SUITE_SCHEDULER]
113
114 server = dbmodels.ForeignKey(Server, related_name='roles')
115 role = dbmodels.CharField(max_length=128, choices=ROLE.choices())
116
117 objects = model_logic.ExtendedManager()
118
119 class Meta:
120 """Metadata for the ServerRole class."""
121 db_table = 'server_roles'
122
123
124class ServerAttribute(dbmodels.Model, model_logic.ModelExtensions):
125 """Attribute associated with hosts."""
126 server = dbmodels.ForeignKey(Server, related_name='attributes')
127 attribute = dbmodels.CharField(max_length=128)
128 value = dbmodels.TextField(null=True, blank=True)
129 date_modified = dbmodels.DateTimeField(null=True, blank=True)
130
131 objects = model_logic.ExtendedManager()
132
133 class Meta:
134 """Metadata for the ServerAttribute class."""
135 db_table = 'server_attributes'
136
137
138# Valid values for each type of input.
139RANGE_LIMITS={'role': ServerRole.ROLE_LIST,
140 'status': Server.STATUS_LIST}
141
142def validate(**kwargs):
143 """Verify command line arguments, raise InvalidDataError if any is invalid.
144
145 The function verify following inputs for the database query.
146 1. Any key in RANGE_LIMITS, i.e., role and status. Value should be a valid
147 role or status.
148 2. hostname. The code will try to resolve given hostname. If the hostname
149 does not exist in the network, InvalidDataError will be raised.
150 Sample usage of this function:
151 validate(role='drone', status='backup', hostname='server1')
152
153 @param kwargs: command line arguments, e.g., `status='primary'`
154 @raise InvalidDataError: If any argument value is invalid.
155 """
156 for key, value in kwargs.items():
157 # Ignore any None value, so callers won't need to filter out None
158 # value as it won't be used in queries.
159 if not value:
160 continue
161 if value not in RANGE_LIMITS.get(key, [value]):
162 raise error.InvalidDataError(
163 '%s %s is not valid, it must be one of %s.' %
164 (key, value,
165 ', '.join(RANGE_LIMITS[key])))
166 elif key == 'hostname':
Dan Shi0697ccd2015-01-05 12:07:38 -0800167 if not ping_runner.PingRunner().simple_ping(value):
168 raise error.InvalidDataError('Can not reach server with '
169 'hostname "%s".' % value)