xref: /aosp_15_r20/external/autotest/frontend/server/models.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
3*9c5db199SXin Li# found in the LICENSE file.
4*9c5db199SXin Li
5*9c5db199SXin Li"""Django model for server database.
6*9c5db199SXin Li"""
7*9c5db199SXin Li
8*9c5db199SXin Lifrom django.db import models as dbmodels
9*9c5db199SXin Li
10*9c5db199SXin Liimport common
11*9c5db199SXin Lifrom autotest_lib.client.common_lib import autotest_enum
12*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
13*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros.network import ping_runner
14*9c5db199SXin Lifrom autotest_lib.frontend.afe import model_logic
15*9c5db199SXin Li
16*9c5db199SXin Li
17*9c5db199SXin Liclass Server(dbmodels.Model, model_logic.ModelExtensions):
18*9c5db199SXin Li    """Models a server."""
19*9c5db199SXin Li    DETAIL_FMT = ('Hostname     : %(hostname)s\n'
20*9c5db199SXin Li                  'Status       : %(status)s\n'
21*9c5db199SXin Li                  'Roles        : %(roles)s\n'
22*9c5db199SXin Li                  'Attributes   : %(attributes)s\n'
23*9c5db199SXin Li                  'Date Created : %(date_created)s\n'
24*9c5db199SXin Li                  'Date Modified: %(date_modified)s\n'
25*9c5db199SXin Li                  'Note         : %(note)s\n')
26*9c5db199SXin Li
27*9c5db199SXin Li    STATUS_LIST = ['primary', 'repair_required']
28*9c5db199SXin Li    STATUS = autotest_enum.AutotestEnum(*STATUS_LIST, string_values=True)
29*9c5db199SXin Li
30*9c5db199SXin Li    hostname = dbmodels.CharField(unique=True, max_length=128)
31*9c5db199SXin Li    cname = dbmodels.CharField(null=True, blank=True, default=None,
32*9c5db199SXin Li                               max_length=128)
33*9c5db199SXin Li    status = dbmodels.CharField(unique=False, max_length=128,
34*9c5db199SXin Li                                choices=STATUS.choices())
35*9c5db199SXin Li    date_created = dbmodels.DateTimeField(null=True, blank=True)
36*9c5db199SXin Li    date_modified = dbmodels.DateTimeField(null=True, blank=True)
37*9c5db199SXin Li    note = dbmodels.TextField(null=True, blank=True)
38*9c5db199SXin Li
39*9c5db199SXin Li    objects = model_logic.ExtendedManager()
40*9c5db199SXin Li
41*9c5db199SXin Li    class Meta:
42*9c5db199SXin Li        """Metadata for class Server."""
43*9c5db199SXin Li        db_table = 'servers'
44*9c5db199SXin Li
45*9c5db199SXin Li
46*9c5db199SXin Li    def __unicode__(self):
47*9c5db199SXin Li        """A string representation of the Server object.
48*9c5db199SXin Li        """
49*9c5db199SXin Li        roles = ','.join([r.role for r in self.roles.all()])
50*9c5db199SXin Li        attributes = dict([(a.attribute, a.value)
51*9c5db199SXin Li                           for a in self.attributes.all()])
52*9c5db199SXin Li        return self.DETAIL_FMT % {'hostname': self.hostname,
53*9c5db199SXin Li                                  'status': self.status,
54*9c5db199SXin Li                                  'roles': roles,
55*9c5db199SXin Li                                  'attributes': attributes,
56*9c5db199SXin Li                                  'date_created': self.date_created,
57*9c5db199SXin Li                                  'date_modified': self.date_modified,
58*9c5db199SXin Li                                  'note': self.note}
59*9c5db199SXin Li
60*9c5db199SXin Li
61*9c5db199SXin Li    def get_role_names(self):
62*9c5db199SXin Li        """Get a list of role names of the server.
63*9c5db199SXin Li
64*9c5db199SXin Li        @return: A list of role names of the server.
65*9c5db199SXin Li        """
66*9c5db199SXin Li        return [r.role for r in self.roles.all()]
67*9c5db199SXin Li
68*9c5db199SXin Li
69*9c5db199SXin Li    def get_details(self):
70*9c5db199SXin Li        """Get a dictionary with all server details.
71*9c5db199SXin Li
72*9c5db199SXin Li        For example:
73*9c5db199SXin Li        {
74*9c5db199SXin Li            'hostname': 'server1',
75*9c5db199SXin Li            'status': 'primary',
76*9c5db199SXin Li            'roles': ['drone', 'scheduler'],
77*9c5db199SXin Li            'attributes': {'max_processes': 300}
78*9c5db199SXin Li        }
79*9c5db199SXin Li
80*9c5db199SXin Li        @return: A dictionary with all server details.
81*9c5db199SXin Li        """
82*9c5db199SXin Li        details = {}
83*9c5db199SXin Li        details['hostname'] = self.hostname
84*9c5db199SXin Li        details['status'] = self.status
85*9c5db199SXin Li        details['roles'] = self.get_role_names()
86*9c5db199SXin Li        attributes = dict([(a.attribute, a.value)
87*9c5db199SXin Li                           for a in self.attributes.all()])
88*9c5db199SXin Li        details['attributes'] = attributes
89*9c5db199SXin Li        details['date_created'] = self.date_created
90*9c5db199SXin Li        details['date_modified'] = self.date_modified
91*9c5db199SXin Li        details['note'] = self.note
92*9c5db199SXin Li        return details
93*9c5db199SXin Li
94*9c5db199SXin Li
95*9c5db199SXin Liclass ServerRole(dbmodels.Model, model_logic.ModelExtensions):
96*9c5db199SXin Li    """Role associated with hosts."""
97*9c5db199SXin Li    # Valid roles for a server.
98*9c5db199SXin Li    # TODO b:169251326 terms below are set outside of this codebase
99*9c5db199SXin Li    # and should be updated when possible.
100*9c5db199SXin Li    ROLE_LIST = [
101*9c5db199SXin Li            'afe',
102*9c5db199SXin Li            'crash_server',
103*9c5db199SXin Li            'database',
104*9c5db199SXin Li            'database_slave', # nocheck
105*9c5db199SXin Li            'devserver',
106*9c5db199SXin Li            'drone',
107*9c5db199SXin Li            'golo_proxy',
108*9c5db199SXin Li            'host_scheduler',
109*9c5db199SXin Li            'scheduler',
110*9c5db199SXin Li            'sentinel',
111*9c5db199SXin Li            'shard',
112*9c5db199SXin Li            'skylab_drone',
113*9c5db199SXin Li
114*9c5db199SXin Li            'reserve',
115*9c5db199SXin Li    ]
116*9c5db199SXin Li    ROLE = autotest_enum.AutotestEnum(*ROLE_LIST, string_values=True)
117*9c5db199SXin Li    # Roles that must be assigned to a single primary server in an Autotest
118*9c5db199SXin Li    # instance
119*9c5db199SXin Li    ROLES_REQUIRE_UNIQUE_INSTANCE = [ROLE.SCHEDULER,
120*9c5db199SXin Li                                     ROLE.HOST_SCHEDULER,
121*9c5db199SXin Li                                     ROLE.DATABASE]
122*9c5db199SXin Li
123*9c5db199SXin Li    server = dbmodels.ForeignKey(Server, related_name='roles')
124*9c5db199SXin Li    role = dbmodels.CharField(max_length=128, choices=ROLE.choices())
125*9c5db199SXin Li
126*9c5db199SXin Li    objects = model_logic.ExtendedManager()
127*9c5db199SXin Li
128*9c5db199SXin Li    class Meta:
129*9c5db199SXin Li        """Metadata for the ServerRole class."""
130*9c5db199SXin Li        db_table = 'server_roles'
131*9c5db199SXin Li
132*9c5db199SXin Li
133*9c5db199SXin Liclass ServerAttribute(dbmodels.Model, model_logic.ModelExtensions):
134*9c5db199SXin Li    """Attribute associated with hosts."""
135*9c5db199SXin Li    server = dbmodels.ForeignKey(Server, related_name='attributes')
136*9c5db199SXin Li    attribute = dbmodels.CharField(max_length=128)
137*9c5db199SXin Li    value = dbmodels.TextField(null=True, blank=True)
138*9c5db199SXin Li    date_modified = dbmodels.DateTimeField(null=True, blank=True)
139*9c5db199SXin Li
140*9c5db199SXin Li    objects = model_logic.ExtendedManager()
141*9c5db199SXin Li
142*9c5db199SXin Li    class Meta:
143*9c5db199SXin Li        """Metadata for the ServerAttribute class."""
144*9c5db199SXin Li        db_table = 'server_attributes'
145*9c5db199SXin Li
146*9c5db199SXin Li
147*9c5db199SXin Li# Valid values for each type of input.
148*9c5db199SXin LiRANGE_LIMITS={'role': ServerRole.ROLE_LIST,
149*9c5db199SXin Li              'status': Server.STATUS_LIST}
150*9c5db199SXin Li
151*9c5db199SXin Lidef validate(**kwargs):
152*9c5db199SXin Li    """Verify command line arguments, raise InvalidDataError if any is invalid.
153*9c5db199SXin Li
154*9c5db199SXin Li    The function verify following inputs for the database query.
155*9c5db199SXin Li    1. Any key in RANGE_LIMITS, i.e., role and status. Value should be a valid
156*9c5db199SXin Li       role or status.
157*9c5db199SXin Li    2. hostname. The code will try to resolve given hostname. If the hostname
158*9c5db199SXin Li       does not exist in the network, InvalidDataError will be raised.
159*9c5db199SXin Li    Sample usage of this function:
160*9c5db199SXin Li    validate(role='drone', status='repair_required', hostname='server1')
161*9c5db199SXin Li
162*9c5db199SXin Li    @param kwargs: command line arguments, e.g., `status='primary'`
163*9c5db199SXin Li    @raise InvalidDataError: If any argument value is invalid.
164*9c5db199SXin Li    """
165*9c5db199SXin Li    for key, value in kwargs.items():
166*9c5db199SXin Li        # Ignore any None value, so callers won't need to filter out None
167*9c5db199SXin Li        # value as it won't be used in queries.
168*9c5db199SXin Li        if not value:
169*9c5db199SXin Li            continue
170*9c5db199SXin Li        if value not in RANGE_LIMITS.get(key, [value]):
171*9c5db199SXin Li            raise error.InvalidDataError(
172*9c5db199SXin Li                    '%s %s is not valid, it must be one of %s.' %
173*9c5db199SXin Li                    (key, value,
174*9c5db199SXin Li                     ', '.join(RANGE_LIMITS[key])))
175*9c5db199SXin Li        elif key == 'hostname':
176*9c5db199SXin Li            if not ping_runner.PingRunner().simple_ping(value):
177*9c5db199SXin Li                raise error.InvalidDataError('Can not reach server with '
178*9c5db199SXin Li                                             'hostname "%s".' % value)
179