models.py revision 91f58e141f3773acf16ae4af3173366b299f76bb
1# 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
8from django.db import models as dbmodels
9
10import common
11from autotest_lib.client.common_lib import enum
12from autotest_lib.client.common_lib import error
13from autotest_lib.client.common_lib.cros.network import ping_runner
14from 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', '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
61    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
69    def get_details(self):
70        """Get a dictionary with all server details.
71
72        For example:
73        {
74            'hostname': 'server1',
75            'status': 'primary',
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
95class ServerRole(dbmodels.Model, model_logic.ModelExtensions):
96    """Role associated with hosts."""
97    # Valid roles for a server.
98    ROLE_LIST = ['afe', 'scheduler', 'host_scheduler', 'drone', 'devserver',
99                 'database', 'database_slave', 'suite_scheduler',
100                 'crash_server', 'shard', 'golo_proxy', 'sentinel', 'reserve']
101    ROLE = enum.Enum(*ROLE_LIST, string_values=True)
102    # Roles that must be assigned to a single primary server in an Autotest
103    # instance
104    ROLES_REQUIRE_UNIQUE_INSTANCE = [ROLE.SCHEDULER,
105                                     ROLE.HOST_SCHEDULER,
106                                     ROLE.DATABASE,
107                                     ROLE.SUITE_SCHEDULER]
108
109    server = dbmodels.ForeignKey(Server, related_name='roles')
110    role = dbmodels.CharField(max_length=128, choices=ROLE.choices())
111
112    objects = model_logic.ExtendedManager()
113
114    class Meta:
115        """Metadata for the ServerRole class."""
116        db_table = 'server_roles'
117
118
119class ServerAttribute(dbmodels.Model, model_logic.ModelExtensions):
120    """Attribute associated with hosts."""
121    server = dbmodels.ForeignKey(Server, related_name='attributes')
122    attribute = dbmodels.CharField(max_length=128)
123    value = dbmodels.TextField(null=True, blank=True)
124    date_modified = dbmodels.DateTimeField(null=True, blank=True)
125
126    objects = model_logic.ExtendedManager()
127
128    class Meta:
129        """Metadata for the ServerAttribute class."""
130        db_table = 'server_attributes'
131
132
133# Valid values for each type of input.
134RANGE_LIMITS={'role': ServerRole.ROLE_LIST,
135              'status': Server.STATUS_LIST}
136
137def validate(**kwargs):
138    """Verify command line arguments, raise InvalidDataError if any is invalid.
139
140    The function verify following inputs for the database query.
141    1. Any key in RANGE_LIMITS, i.e., role and status. Value should be a valid
142       role or status.
143    2. hostname. The code will try to resolve given hostname. If the hostname
144       does not exist in the network, InvalidDataError will be raised.
145    Sample usage of this function:
146    validate(role='drone', status='repair_required', hostname='server1')
147
148    @param kwargs: command line arguments, e.g., `status='primary'`
149    @raise InvalidDataError: If any argument value is invalid.
150    """
151    for key, value in kwargs.items():
152        # Ignore any None value, so callers won't need to filter out None
153        # value as it won't be used in queries.
154        if not value:
155            continue
156        if value not in RANGE_LIMITS.get(key, [value]):
157            raise error.InvalidDataError(
158                    '%s %s is not valid, it must be one of %s.' %
159                    (key, value,
160                     ', '.join(RANGE_LIMITS[key])))
161        elif key == 'hostname':
162            if not ping_runner.PingRunner().simple_ping(value):
163                raise error.InvalidDataError('Can not reach server with '
164                                             'hostname "%s".' % value)
165