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