19a535c9f3144690cf85d88a51e07b98c21454159Dan Shi# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
29a535c9f3144690cf85d88a51e07b98c21454159Dan Shi# Use of this source code is governed by a BSD-style license that can be
39a535c9f3144690cf85d88a51e07b98c21454159Dan Shi# found in the LICENSE file.
49a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
59a535c9f3144690cf85d88a51e07b98c21454159Dan Shi"""Django model for server database.
69a535c9f3144690cf85d88a51e07b98c21454159Dan Shi"""
79a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
89a535c9f3144690cf85d88a51e07b98c21454159Dan Shifrom django.db import models as dbmodels
99a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
109a535c9f3144690cf85d88a51e07b98c21454159Dan Shiimport common
119a535c9f3144690cf85d88a51e07b98c21454159Dan Shifrom autotest_lib.client.common_lib import enum
129a535c9f3144690cf85d88a51e07b98c21454159Dan Shifrom autotest_lib.client.common_lib import error
130697ccd2453b9eafc087e1e6a7bdb289b81b45edDan Shifrom autotest_lib.client.common_lib.cros.network import ping_runner
149a535c9f3144690cf85d88a51e07b98c21454159Dan Shifrom autotest_lib.frontend.afe import model_logic
159a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
169a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
179a535c9f3144690cf85d88a51e07b98c21454159Dan Shiclass Server(dbmodels.Model, model_logic.ModelExtensions):
189a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    """Models a server."""
199a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    DETAIL_FMT = ('Hostname     : %(hostname)s\n'
209a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                  'Status       : %(status)s\n'
219a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                  'Roles        : %(roles)s\n'
229a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                  'Attributes   : %(attributes)s\n'
239a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                  'Date Created : %(date_created)s\n'
249a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                  'Date Modified: %(date_modified)s\n'
259a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                  'Note         : %(note)s\n')
269a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
2791f58e141f3773acf16ae4af3173366b299f76bbXixuan Wu    STATUS_LIST = ['primary', 'repair_required']
289a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    STATUS = enum.Enum(*STATUS_LIST, string_values=True)
299a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
309a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    hostname = dbmodels.CharField(unique=True, max_length=128)
319a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    cname = dbmodels.CharField(null=True, blank=True, default=None,
329a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                               max_length=128)
339a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    status = dbmodels.CharField(unique=False, max_length=128,
349a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                                choices=STATUS.choices())
359a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    date_created = dbmodels.DateTimeField(null=True, blank=True)
369a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    date_modified = dbmodels.DateTimeField(null=True, blank=True)
379a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    note = dbmodels.TextField(null=True, blank=True)
389a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
399a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    objects = model_logic.ExtendedManager()
409a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
419a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    class Meta:
429a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        """Metadata for class Server."""
439a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        db_table = 'servers'
449a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
459a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
469a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    def __unicode__(self):
479a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        """A string representation of the Server object.
489a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        """
499a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        roles = ','.join([r.role for r in self.roles.all()])
509a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        attributes = dict([(a.attribute, a.value)
519a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                           for a in self.attributes.all()])
529a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        return self.DETAIL_FMT % {'hostname': self.hostname,
539a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                                  'status': self.status,
549a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                                  'roles': roles,
559a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                                  'attributes': attributes,
569a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                                  'date_created': self.date_created,
579a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                                  'date_modified': self.date_modified,
589a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                                  'note': self.note}
599a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
609a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
6156f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi    def get_role_names(self):
6256f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi        """Get a list of role names of the server.
6356f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi
6456f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi        @return: A list of role names of the server.
6556f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi        """
6656f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi        return [r.role for r in self.roles.all()]
6756f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi
6856f1ba77e4b8b4c13d5bc72b0ebaeabda9f9d0bcDan Shi
69d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi    def get_details(self):
70d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        """Get a dictionary with all server details.
71d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi
72d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        For example:
73d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        {
74d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi            'hostname': 'server1',
7591f58e141f3773acf16ae4af3173366b299f76bbXixuan Wu            'status': 'primary',
76d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi            'roles': ['drone', 'scheduler'],
77d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi            'attributes': {'max_processes': 300}
78d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        }
79d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi
80d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        @return: A dictionary with all server details.
81d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        """
82d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        details = {}
83d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        details['hostname'] = self.hostname
84d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        details['status'] = self.status
85d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        details['roles'] = self.get_role_names()
86d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        attributes = dict([(a.attribute, a.value)
87d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi                           for a in self.attributes.all()])
88d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        details['attributes'] = attributes
89d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        details['date_created'] = self.date_created
90d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        details['date_modified'] = self.date_modified
91d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        details['note'] = self.note
92d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi        return details
93d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi
94d7bb4f16fde2f29efac95b0f7b8122b05ea4bf03Dan Shi
959a535c9f3144690cf85d88a51e07b98c21454159Dan Shiclass ServerRole(dbmodels.Model, model_logic.ModelExtensions):
969a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    """Role associated with hosts."""
979a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    # Valid roles for a server.
98a09a37a6208ad09b0dbcd2ad0da5ad4c41c24144Fang Deng    ROLE_LIST = ['afe', 'scheduler', 'host_scheduler', 'drone', 'devserver',
9993e646cbcab150b4ae11945592ff969e464ad31aXixuan Wu                 'database', 'database_slave', 'crash_server', 'shard',
10093e646cbcab150b4ae11945592ff969e464ad31aXixuan Wu                 'golo_proxy', 'sentinel', 'reserve']
1019a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    ROLE = enum.Enum(*ROLE_LIST, string_values=True)
1029a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    # Roles that must be assigned to a single primary server in an Autotest
1039a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    # instance
1049a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    ROLES_REQUIRE_UNIQUE_INSTANCE = [ROLE.SCHEDULER,
1059a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                                     ROLE.HOST_SCHEDULER,
10693e646cbcab150b4ae11945592ff969e464ad31aXixuan Wu                                     ROLE.DATABASE]
1079a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1089a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    server = dbmodels.ForeignKey(Server, related_name='roles')
1099a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    role = dbmodels.CharField(max_length=128, choices=ROLE.choices())
1109a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1119a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    objects = model_logic.ExtendedManager()
1129a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1139a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    class Meta:
1149a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        """Metadata for the ServerRole class."""
1159a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        db_table = 'server_roles'
1169a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1179a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1189a535c9f3144690cf85d88a51e07b98c21454159Dan Shiclass ServerAttribute(dbmodels.Model, model_logic.ModelExtensions):
1199a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    """Attribute associated with hosts."""
1209a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    server = dbmodels.ForeignKey(Server, related_name='attributes')
1219a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    attribute = dbmodels.CharField(max_length=128)
1229a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    value = dbmodels.TextField(null=True, blank=True)
1239a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    date_modified = dbmodels.DateTimeField(null=True, blank=True)
1249a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1259a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    objects = model_logic.ExtendedManager()
1269a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1279a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    class Meta:
1289a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        """Metadata for the ServerAttribute class."""
1299a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        db_table = 'server_attributes'
1309a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1319a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1329a535c9f3144690cf85d88a51e07b98c21454159Dan Shi# Valid values for each type of input.
1339a535c9f3144690cf85d88a51e07b98c21454159Dan ShiRANGE_LIMITS={'role': ServerRole.ROLE_LIST,
1349a535c9f3144690cf85d88a51e07b98c21454159Dan Shi              'status': Server.STATUS_LIST}
1359a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1369a535c9f3144690cf85d88a51e07b98c21454159Dan Shidef validate(**kwargs):
1379a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    """Verify command line arguments, raise InvalidDataError if any is invalid.
1389a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1399a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    The function verify following inputs for the database query.
1409a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    1. Any key in RANGE_LIMITS, i.e., role and status. Value should be a valid
1419a535c9f3144690cf85d88a51e07b98c21454159Dan Shi       role or status.
1429a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    2. hostname. The code will try to resolve given hostname. If the hostname
1439a535c9f3144690cf85d88a51e07b98c21454159Dan Shi       does not exist in the network, InvalidDataError will be raised.
1449a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    Sample usage of this function:
14591f58e141f3773acf16ae4af3173366b299f76bbXixuan Wu    validate(role='drone', status='repair_required', hostname='server1')
1469a535c9f3144690cf85d88a51e07b98c21454159Dan Shi
1479a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    @param kwargs: command line arguments, e.g., `status='primary'`
1489a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    @raise InvalidDataError: If any argument value is invalid.
1499a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    """
1509a535c9f3144690cf85d88a51e07b98c21454159Dan Shi    for key, value in kwargs.items():
1519a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        # Ignore any None value, so callers won't need to filter out None
1529a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        # value as it won't be used in queries.
1539a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        if not value:
1549a535c9f3144690cf85d88a51e07b98c21454159Dan Shi            continue
1559a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        if value not in RANGE_LIMITS.get(key, [value]):
1569a535c9f3144690cf85d88a51e07b98c21454159Dan Shi            raise error.InvalidDataError(
1579a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                    '%s %s is not valid, it must be one of %s.' %
1589a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                    (key, value,
1599a535c9f3144690cf85d88a51e07b98c21454159Dan Shi                     ', '.join(RANGE_LIMITS[key])))
1609a535c9f3144690cf85d88a51e07b98c21454159Dan Shi        elif key == 'hostname':
1610697ccd2453b9eafc087e1e6a7bdb289b81b45edDan Shi            if not ping_runner.PingRunner().simple_ping(value):
1620697ccd2453b9eafc087e1e6a7bdb289b81b45edDan Shi                raise error.InvalidDataError('Can not reach server with '
1630697ccd2453b9eafc087e1e6a7bdb289b81b45edDan Shi                                             'hostname "%s".' % value)
164