1##########################################################################
2# Copyright (c) 2009, ETH Zurich.
3# All rights reserved.
4#
5# This file is distributed under the terms in the attached LICENSE file.
6# If you do not find this file, copies can be found by writing to:
7# ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
8##########################################################################
9
10import sys, os, signal, time, getpass, subprocess, socket, pty
11import debug, machines, msrc_machinedata
12from machines import Machine, MachineLockedError, MachineFactory
13
14TFTP_PATH='/tftpboot'
15TOOLS_PATH='/home/netos/tools/bin'
16RACKBOOT=os.path.join(TOOLS_PATH, 'rackboot.sh')
17RACKPOWER=os.path.join(TOOLS_PATH, 'rackpower')
18
19class MSRCMachine(Machine):
20    _msrc_machines = msrc_machinedata.machines
21
22    def __init__(self, options):
23        super(MSRCMachine, self).__init__(options)
24        self.lockprocess = None
25
26    def get_bootarch(self):
27        b = self._msrc_machines[self.name]['bootarch']
28        assert(b in self.get_buildarchs())
29        return b
30
31    def get_machine_name(self):
32        return self._msrc_machines[self.name]['machine_name']
33
34    def get_buildarchs(self):
35        return self._msrc_machines[self.name]['buildarchs']
36
37    def get_ncores(self):
38        return self._msrc_machines[self.name]['ncores']
39
40    def get_cores_per_socket(self):
41        return self._msrc_machines[self.name]['cores_per_socket']
42
43    def get_tickrate(self):
44        return self._msrc_machines[self.name]['tickrate']
45
46    def get_perfcount_type(self):
47        return self._msrc_machines[self.name]['perfcount_type']
48
49    def get_hostname(self):
50        return self.get_machine_name()
51
52    def get_ip(self):
53        return socket.gethostbyname(self.get_hostname())
54
55    def get_tftp_dir(self):
56        user = getpass.getuser()
57        return os.path.join(TFTP_PATH, user, self.name + "_harness")
58
59    def _write_menu_lst(self, data, path):
60        debug.verbose('writing %s' % path)
61        debug.debug(data)
62        f = open(path, 'w')
63        f.write(data)
64        f.close()
65
66    def _set_menu_lst(self, relpath):
67        ip_menu_name = os.path.join(TFTP_PATH, "menu.lst." + self.get_ip())
68        debug.verbose('relinking %s to %s' % (ip_menu_name, relpath))
69        os.remove(ip_menu_name)
70        os.symlink(relpath, ip_menu_name)
71
72    def set_bootmodules(self, modules):
73        fullpath = os.path.join(self.get_tftp_dir(), 'menu.lst')
74        relpath = os.path.relpath(fullpath, TFTP_PATH)
75        tftppath = '/' + os.path.relpath(self.get_tftp_dir(), TFTP_PATH)
76        self._write_menu_lst(modules.get_menu_data(tftppath), fullpath)
77        self._set_menu_lst(relpath)
78
79    def lock(self):
80        """Use conserver to lock the machine."""
81
82        # find out current status of console
83        debug.verbose('executing "console -i %s" to check state' %
84                      self.get_machine_name())
85        proc = subprocess.Popen(["console", "-i", self.get_machine_name()],
86                                stdout=subprocess.PIPE)
87        line = proc.communicate()[0]
88        assert(proc.returncode == 0)
89
90        # check that nobody else has it open for writing
91        myuser = getpass.getuser()
92        parts = line.strip().split(':')
93        conname, child, contype, details, users, state = parts[:6]
94        if users:
95            for userinfo in users.split(','):
96                mode, username, host, port = userinfo.split('@')[:4]
97                if 'w' in mode and username != myuser:
98                    raise MachineLockedError # Machine is not free
99
100        # run a console in the background to 'hold' the lock and read output
101        debug.verbose('starting "console %s"' % self.get_machine_name())
102        # run on a PTY to work around terminal mangling code in console
103        (masterfd, slavefd) = pty.openpty()
104        self.lockprocess = subprocess.Popen(["console", self.get_machine_name()],
105                                            close_fds=True,
106                                            stdout=slavefd, stdin=slavefd)
107        os.close(slavefd)
108        # XXX: open in binary mode with no buffering
109        # otherwise select.select() may block when there is data in the buffer
110        self.console_out = os.fdopen(masterfd, 'rb', 0)
111
112    def unlock(self):
113        if self.lockprocess is None:
114            return # noop
115        debug.verbose('terminating console process (%d)' % self.lockprocess.pid)
116        os.kill(self.lockprocess.pid, signal.SIGTERM)
117        self.lockprocess.wait()
118        self.lockprocess = None
119
120    def __rackboot(self, args):
121        debug.checkcmd([RACKBOOT] + args + [self.get_machine_name()])
122
123    def setup(self):
124        self.__rackboot(["-b", "-n"])
125
126    def __rackpower(self, arg):
127        try:
128            debug.checkcmd([RACKPOWER, arg, self.get_machine_name()])
129        except subprocess.CalledProcessError:
130            debug.warning("rackpower %s %s failed" %
131                          (arg, self.get_machine_name()))
132
133    def reboot(self):
134        self.__rackpower('-r')
135
136    def shutdown(self):
137        self.__rackpower('-d')
138
139    def get_output(self):
140        return self.console_out
141
142
143for n in sorted(MSRCMachine._msrc_machines.keys()):
144    class TmpMachine(MSRCMachine):
145        name = n
146    MachineFactory.addMachine(n, TmpMachine)
147