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