1#!/usr/bin/python3
2
3import argparse
4import pexpect
5import sys
6import time
7
8from collections import namedtuple
9
10Endpoint = namedtuple("Endpoint", "host, port")
11Credentials = namedtuple("Credentials", "username, password")
12Target = namedtuple("Target", "credentials, endpoint")
13Entity = namedtuple("Entity", "console, ssh")
14Machine = namedtuple("Machine", "bmc, host")
15
16class Obmcutil(object):
17    BMC_READY =     "xyz.openbmc_project.State.BMC.BMCState.Ready"
18    BMC_NOT_READY = "xyz.openbmc_project.State.BMC.BMCState.NotReady"
19
20    HOST_OFF = "xyz.openbmc_project.State.Host.HostState.Off"
21    HOST_ON = "xyz.openbmc_project.State.Host.HostState.Running"
22    HOST_QUIESCED = "xyz.openbmc_project.State.Host.HostState.Quiesced"
23
24    def __init__(self, session, prompt):
25        self.session = session
26        self.prompt = prompt
27
28    def _clear(self):
29        self.session.expect([".+".encode(), pexpect.TIMEOUT], timeout=5)
30
31    def _state(self, cmd, needle):
32        self.session.sendline()
33        self._clear()
34        self.session.sendline("obmcutil -w {}".format(cmd).encode())
35        self.session.expect(needle, timeout=None)
36        rc = self.session.after.decode()
37        return rc
38
39    def hoststate(self):
40        return self._state("hoststate", "xyz\.openbmc_project\.State\.Host\.HostState\.(Off|Running|Quiesced)")
41
42    def bmcstate(self):
43        return self._state("bmcstate", "xyz\.openbmc_project\.State\.BMC\.BMCState\.(Not)?Ready")
44
45    def poweron(self):
46        self.session.sendline("obmcutil -w poweron")
47        self.session.expect(self.prompt)
48
49    def chassisoff(self):
50        self.session.sendline("obmcutil -w chassisoff")
51        self.session.expect(self.prompt)
52
53class PexpectLogger(object):
54    def write(self, bstring):
55        try:
56            sys.stdout.write(bstring.decode())
57        except UnicodeDecodeError:
58            print("Dropping broken unicode line")
59
60    def flush(self):
61        sys.stdout.flush()
62
63
64class Bmc(object):
65    def __init__(self, entity):
66        self.getty = "login: ".encode()
67        self.shell = "# ".encode()
68        self.entity = entity
69        fargs = (entity.console.endpoint.host, entity.console.endpoint.port)
70        self.session = pexpect.spawn("telnet {} {}".format(*fargs))
71        self.session.logfile = PexpectLogger()
72        self.obmcutil = Obmcutil(self.session, self.shell)
73        self.session.sendline()
74        rc = self.session.expect([self.getty, self.shell])
75        if rc == 0:
76            self.login()
77
78    def login(self):
79        self.session.sendline(self.entity.console.credentials.username.encode())
80        self.session.expect("Password: ".encode())
81        self.session.sendline(self.entity.console.credentials.password.encode())
82        self.session.expect(self.shell)
83
84    def reboot(self):
85        self.session.sendline("reboot")
86        self.session.expect("Hit any key to stop autoboot:".encode(), timeout=None)
87        self.session.expect(self.getty, timeout=None)
88        self.login()
89        state = self.obmcutil.bmcstate()
90        while state != self.obmcutil.BMC_READY:
91            print("Wanted state '{}', got state '{}'".format(self.obmcutil.BMC_READY, state))
92            time.sleep(5)
93            state = self.obmcutil.bmcstate()
94
95    def chassisoff(self):
96        self.obmcutil.chassisoff()
97
98    def poweron(self):
99        hs = self.obmcutil.hoststate()
100        print("Host state is: {}".format(hs))
101        if hs in (self.obmcutil.HOST_ON, self.obmcutil.HOST_QUIESCED):
102            self.obmcutil.chassisoff()
103        self.obmcutil.poweron()
104
105class Host(object):
106    def __init__(self, entity, bmc):
107        self.shell = "/? *#".encode()
108        self.petitboot = "Petitboot".encode()
109        self.session = None
110        self.entity = entity
111        self.bmc = bmc
112        self.connect()
113
114    def connect(self):
115        fargs = (self.entity.console.endpoint.port,
116                self.entity.console.credentials.username,
117                self.entity.console.endpoint.host)
118        self.session = pexpect.spawn("ssh -p{} {}@{}".format(*fargs))
119        self.session.logfile = PexpectLogger()
120        self.session.expect("password:".encode())
121        self.session.sendline(self.entity.console.credentials.password.encode())
122
123    def poweron(self):
124        self.bmc.chassisoff()
125        self.bmc.poweron()
126        self.session.send('\f')
127        rc = self.session.expect([self.petitboot, self.shell], timeout=None)
128        if rc == 0:
129            self.session.sendline()
130            self.session.expect(self.shell)
131
132    def reboot(self):
133        self.session.send('\f')
134        rc = self.session.expect([self.petitboot, self.shell], timeout=None)
135        if rc == 0:
136            self.session.sendline()
137            self.session.expect(self.shell)
138        self.session.sendline("reboot".encode())
139        self.session.expect("INIT: Waiting for kernel...".encode(), timeout=None)
140        self.session.expect("Petitboot".encode(), timeout=None)
141        self.session.sendline()
142        self.session.expect(self.shell)
143
144def rpp(machine):
145    bmc = Bmc(machine.bmc)
146    host = Host(machine.host, bmc)
147    host.poweron()
148    while True:
149        bmc.reboot()
150        host.connect()
151        host.reboot()
152
153def main():
154    bmccreds = Credentials("root", "0penBmc")
155    b = Entity(Target(bmccreds, Endpoint("serial.concentrator.somewhere.com", 1234)),
156            Target(bmccreds, Endpoint("bmc.somewhere.com", 22)))
157    h = Entity(Target(bmccreds, Endpoint("bmc.somewhere.com", 2200)),
158            Target(Credentials("user", "password"), Endpoint("host.somewhere.com", 22)))
159    m = Machine(b, h)
160    return rpp(m)
161
162if __name__ == "__main__":
163    main()
164