Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 1 | """ |
| 2 | Copyright 2021 Open Networking Foundation (ONF) |
| 3 | |
| 4 | Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>, |
| 5 | the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>, |
| 6 | or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg> |
| 7 | |
| 8 | """ |
| 9 | |
| 10 | import pexpect |
| 11 | import os |
| 12 | from drivers.common.clidriver import CLI |
| 13 | |
| 14 | |
| 15 | class P4RuntimeCliDriver(CLI): |
| 16 | """ |
| 17 | Implements a P4Runtime Client CLI-based driver to control devices that uses |
| 18 | the P4Runtime Protocol, using the P4Runtime shell CLI. |
| 19 | |
| 20 | This driver requires that P4Runtime CLI is configured to not generated colored |
| 21 | text. To do so, add the following lines to a file in |
| 22 | ~/.ipython/profile_default/ipython_config.py: |
| 23 | c.InteractiveShell.color_info = False |
| 24 | c.InteractiveShell.colors = 'NoColor' |
| 25 | c.TerminalInteractiveShell.color_info = False |
| 26 | c.TerminalInteractiveShell.colors = 'NoColor' |
| 27 | """ |
| 28 | |
| 29 | def __init__(self): |
| 30 | """ |
| 31 | Initialize client |
| 32 | """ |
| 33 | super(P4RuntimeCliDriver, self).__init__() |
| 34 | self.name = None |
| 35 | self.handle = None |
| 36 | self.p4rtAddress = "localhost" |
| 37 | self.p4rtPort = "9559" # Default P4RT server port |
| 38 | self.prompt = "\$" |
| 39 | self.p4rtShPrompt = ">>>" |
| 40 | self.p4rtDeviceId = "1" |
| 41 | self.p4rtElectionId = "0,100" # (high,low) |
| 42 | self.p4rtConfig = None # Can be used to pass a path to the P4Info and pipeline config |
| 43 | |
| 44 | def connect(self, **connectargs): |
| 45 | """ |
| 46 | Creates the ssh handle for the P4Runtime CLI |
| 47 | The ip_address would come from the topo file using the host tag, the |
| 48 | value can be an environment variable as well as a "localhost" to get |
| 49 | the ip address needed to ssh to the "bench" |
| 50 | """ |
| 51 | try: |
| 52 | for key in connectargs: |
| 53 | vars(self)[key] = connectargs[key] |
| 54 | self.name = self.options.get("name", "") |
| 55 | self.p4rtAddress = self.options.get("p4rt_address", "localhost") |
| 56 | self.p4rtPort = self.options.get("p4rt_port", "9559") |
| 57 | self.p4rtShPrompt = self.options.get("p4rt_sh_prompt", ">>>") |
| 58 | self.p4rtDeviceId = self.options.get("p4rt_device_id", "1") |
| 59 | self.p4rtElectionId = self.options.get("p4rt_election_id", "0,100") |
| 60 | self.p4rtConfig = self.options.get("p4rt_config", None) |
| 61 | try: |
| 62 | if os.getenv(str(self.ip_address)) is not None: |
| 63 | self.ip_address = os.getenv(str(self.ip_address)) |
| 64 | else: |
| 65 | main.log.info(self.name + ": ip set to " + self.ip_address) |
| 66 | except KeyError: |
| 67 | main.log.info(self.name + ": Invalid host name," + |
| 68 | "defaulting to 'localhost' instead") |
| 69 | self.ip_address = 'localhost' |
| 70 | except Exception as inst: |
| 71 | main.log.error("Uncaught exception: " + str(inst)) |
| 72 | |
| 73 | self.handle = super(P4RuntimeCliDriver, self).connect( |
| 74 | user_name=self.user_name, |
| 75 | ip_address=self.ip_address, |
| 76 | port=None, |
| 77 | pwd=self.pwd) |
| 78 | if self.handle: |
| 79 | main.log.info("Connection successful to the host " + |
| 80 | self.user_name + |
| 81 | "@" + |
| 82 | self.ip_address) |
| 83 | self.handle.sendline("") |
| 84 | self.handle.expect(self.prompt) |
| 85 | return main.TRUE |
| 86 | else: |
| 87 | main.log.error("Connection failed to " + |
| 88 | self.user_name + |
| 89 | "@" + |
| 90 | self.ip_address) |
| 91 | return main.FALSE |
| 92 | except pexpect.EOF: |
| 93 | main.log.error(self.name + ": EOF exception found") |
| 94 | main.log.error(self.name + ": " + self.handle.before) |
| 95 | main.cleanAndExit() |
| 96 | except Exception: |
| 97 | main.log.exception(self.name + ": Uncaught exception!") |
| 98 | main.cleanAndExit() |
| 99 | |
| 100 | def startP4RtClient(self, pushConfig=False): |
| 101 | """ |
| 102 | Start the P4Runtime shell CLI client |
| 103 | |
| 104 | :param pushConfig: True if you want to push the pipeline config, False otherwise |
| 105 | requires the p4rt_config configuration parameter to be set |
| 106 | :return: |
| 107 | """ |
| 108 | try: |
| 109 | main.log.debug(self.name + ": Starting P4Runtime Shell CLI") |
| 110 | grpcAddr = "%s:%s" % (self.p4rtAddress, self.p4rtPort) |
Daniele Moro | 54fd002 | 2021-10-15 17:40:27 +0200 | [diff] [blame] | 111 | startP4RtShLine = "python3 -m p4runtime_sh -v --grpc-addr " + grpcAddr + \ |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 112 | " --device-id " + self.p4rtDeviceId + \ |
| 113 | " --election-id " + self.p4rtElectionId |
| 114 | if pushConfig: |
| 115 | if self.p4rtConfig: |
| 116 | startP4RtShLine += " --config " + self.p4rtConfig |
| 117 | else: |
| 118 | main.log.error( |
| 119 | "You should provide a P4 Runtime config to push!") |
| 120 | main.cleanAndExit() |
| 121 | response = self.__clearSendAndExpect(startP4RtShLine) |
Daniele Moro | 54fd002 | 2021-10-15 17:40:27 +0200 | [diff] [blame] | 122 | if "CRITICAL" in response: |
| 123 | main.log.exception(self.name + ": Connection error.") |
| 124 | main.cleanAndExit() |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 125 | self.preDisconnect = self.stopP4RtClient |
| 126 | except pexpect.TIMEOUT: |
| 127 | main.log.exception(self.name + ": Command timed out") |
Jon Hall | a4a7931 | 2022-01-25 17:16:53 -0800 | [diff] [blame] | 128 | main.cleanAndExit() |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 129 | except pexpect.EOF: |
| 130 | main.log.exception(self.name + ": connection closed.") |
| 131 | main.cleanAndExit() |
| 132 | except Exception: |
| 133 | main.log.exception(self.name + ": Uncaught exception!") |
| 134 | main.cleanAndExit() |
| 135 | |
| 136 | def stopP4RtClient(self): |
| 137 | """ |
| 138 | Exit the P4Runtime shell CLI |
| 139 | """ |
| 140 | try: |
| 141 | main.log.debug(self.name + ": Stopping P4Runtime Shell CLI") |
| 142 | self.handle.sendline("exit") |
| 143 | self.handle.expect(self.prompt) |
| 144 | return main.TRUE |
| 145 | except pexpect.TIMEOUT: |
| 146 | main.log.exception(self.name + ": Command timed out") |
| 147 | return main.FALSE |
| 148 | except pexpect.EOF: |
| 149 | main.log.exception(self.name + ": connection closed.") |
| 150 | main.cleanAndExit() |
| 151 | except Exception: |
| 152 | main.log.exception(self.name + ": Uncaught exception!") |
| 153 | main.cleanAndExit() |
| 154 | |
| 155 | def pushTableEntry(self, tableEntry=None, debug=True): |
| 156 | """ |
| 157 | Push a table entry with either the given table entry or use the saved |
| 158 | table entry in the variable 'te'. |
| 159 | |
| 160 | Example of a valid tableEntry string: |
| 161 | te = table_entry["FabricIngress.forwarding.routing_v4"](action="set_next_id_routing_v4"); te.action["next_id"] = "10"; te.match["ipv4_dst"] = "10.0.0.0" # nopep8 |
| 162 | |
| 163 | :param tableEntry: the string table entry, if None it uses the table |
| 164 | entry saved in the 'te' variable |
| 165 | :param debug: True to enable debug logging, False otherwise |
| 166 | :return: main.TRUE or main.FALSE on error |
| 167 | """ |
| 168 | try: |
| 169 | main.log.debug(self.name + ": Pushing Table Entry") |
| 170 | if debug: |
| 171 | self.handle.sendline("te") |
| 172 | self.handle.expect(self.p4rtShPrompt) |
| 173 | pushCmd = "" |
| 174 | if tableEntry: |
| 175 | pushCmd = tableEntry + ";" |
| 176 | pushCmd += "te.insert()" |
| 177 | response = self.__clearSendAndExpect(pushCmd) |
| 178 | if "Traceback" in response or "Error" in response: |
| 179 | # TODO: other possibile errors? |
| 180 | # NameError... |
| 181 | main.log.error( |
| 182 | self.name + ": Error in pushing table entry: " + response) |
| 183 | return main.FALSE |
| 184 | return main.TRUE |
| 185 | except pexpect.TIMEOUT: |
| 186 | main.log.exception(self.name + ": Command timed out") |
| 187 | return main.FALSE |
| 188 | except pexpect.EOF: |
| 189 | main.log.exception(self.name + ": connection closed.") |
| 190 | main.cleanAndExit() |
| 191 | except Exception: |
| 192 | main.log.exception(self.name + ": Uncaught exception!") |
| 193 | main.cleanAndExit() |
| 194 | |
| 195 | def deleteTableEntry(self, tableEntry=None, debug=True): |
| 196 | """ |
| 197 | Deletes a table entry with either the given table entry or use the saved |
| 198 | table entry in the variable 'te'. |
| 199 | |
| 200 | Example of a valid tableEntry string: |
| 201 | te = table_entry["FabricIngress.forwarding.routing_v4"](action="set_next_id_routing_v4"); te.action["next_id"] = "10"; te.match["ipv4_dst"] = "10.0.0.0" # nopep8 |
| 202 | |
| 203 | :param tableEntry: the string table entry, if None it uses the table |
| 204 | entry saved in the 'te' variable |
| 205 | :param debug: True to enable debug logging, False otherwise |
| 206 | :return: main.TRUE or main.FALSE on error |
| 207 | """ |
| 208 | try: |
| 209 | main.log.debug(self.name + ": Deleting Table Entry") |
| 210 | if debug: |
| 211 | self.__clearSendAndExpect("te") |
| 212 | pushCmd = "" |
| 213 | if tableEntry: |
| 214 | pushCmd = tableEntry + ";" |
| 215 | pushCmd += "te.delete()" |
| 216 | response = self.__clearSendAndExpect(pushCmd) |
| 217 | main.log.debug( |
| 218 | self.name + ": Delete table entry response: {}".format( |
| 219 | response)) |
| 220 | if "Traceback" in response or "Error" in response: |
| 221 | # TODO: other possibile errors? |
| 222 | # NameError... |
| 223 | main.log.error( |
| 224 | self.name + ": Error in deleting table entry: " + response) |
| 225 | return main.FALSE |
| 226 | return main.TRUE |
| 227 | except pexpect.TIMEOUT: |
| 228 | main.log.exception(self.name + ": Command timed out") |
| 229 | return main.FALSE |
| 230 | except pexpect.EOF: |
| 231 | main.log.exception(self.name + ": connection closed.") |
| 232 | main.cleanAndExit() |
| 233 | except Exception: |
| 234 | main.log.exception(self.name + ": Uncaught exception!") |
| 235 | main.cleanAndExit() |
| 236 | |
Daniele Moro | b8404e8 | 2022-02-25 00:17:28 +0100 | [diff] [blame] | 237 | def modifyMeterEntry(self, meterEntry=None, debug=True): |
| 238 | """ |
| 239 | Modify a meter entry with either the given meter entry or use the saved |
| 240 | meter entry in the variable 'me'. |
| 241 | |
| 242 | Example of a valid tableEntry string: |
| 243 | me = meter_entry["FabricIngress.upf.app_meter"]; me.cir = 1; me.cburst=1; me.pir=1; me.pburst=1; # nopep8 |
| 244 | |
| 245 | :param meterEntry: the string meter entry, if None it uses the meter |
| 246 | entry saved in the 'me' variable |
| 247 | :param debug: True to enable debug logging, False otherwise |
| 248 | :return: main.TRUE or main.FALSE on error |
| 249 | """ |
| 250 | try: |
| 251 | main.log.debug(self.name + ": Pushing Meter Entry") |
| 252 | if debug: |
| 253 | self.handle.sendline("me") |
| 254 | self.handle.expect(self.p4rtShPrompt) |
| 255 | pushCmd = "" |
| 256 | if meterEntry: |
| 257 | pushCmd = meterEntry + ";" |
| 258 | pushCmd += "me.modify()" |
| 259 | response = self.__clearSendAndExpect(pushCmd) |
| 260 | if "Traceback" in response or "Error" in response or "INVALID_ARGUMENT" in response: |
| 261 | # TODO: other possibile errors? |
| 262 | # NameError... |
| 263 | main.log.error( |
| 264 | self.name + ": Error in modifying meter entry: " + response) |
| 265 | return main.FALSE |
| 266 | return main.TRUE |
| 267 | except pexpect.TIMEOUT: |
| 268 | main.log.exception(self.name + ": Command timed out") |
| 269 | return main.FALSE |
| 270 | except pexpect.EOF: |
| 271 | main.log.exception(self.name + ": connection closed.") |
| 272 | main.cleanAndExit() |
| 273 | except Exception: |
| 274 | main.log.exception(self.name + ": Uncaught exception!") |
| 275 | main.cleanAndExit() |
| 276 | |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 277 | def buildP4RtTableEntry(self, tableName, actionName, actionParams={}, |
Daniele Moro | bef0c7e | 2022-02-16 17:47:13 -0800 | [diff] [blame] | 278 | matchFields={}, priority=0): |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 279 | """ |
| 280 | Build a Table Entry |
| 281 | :param tableName: The name of table |
| 282 | :param actionName: The name of the action |
| 283 | :param actionParams: A dictionary containing name and values for the action parameters |
| 284 | :param matchFields: A dictionary containing name and values for the match fields |
Daniele Moro | bef0c7e | 2022-02-16 17:47:13 -0800 | [diff] [blame] | 285 | :param priority: for ternary match entries |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 286 | :return: main.TRUE or main.FALSE on error |
| 287 | """ |
| 288 | # TODO: improve error checking when creating the table entry, add |
| 289 | # params, and match fields. |
| 290 | try: |
Daniele Moro | bef0c7e | 2022-02-16 17:47:13 -0800 | [diff] [blame] | 291 | main.log.debug("%s: Building P4RT Table Entry " |
| 292 | "(table=%s, match=%s, priority=%s, action=%s, params=%s)" % ( |
Daniele Moro | b8404e8 | 2022-02-25 00:17:28 +0100 | [diff] [blame] | 293 | self.name, tableName, matchFields, priority, |
| 294 | actionName, actionParams)) |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 295 | cmd = 'te = table_entry["%s"](action="%s"); ' % ( |
| 296 | tableName, actionName) |
| 297 | |
| 298 | # Action Parameters |
| 299 | for name, value in actionParams.items(): |
| 300 | cmd += 'te.action["%s"]="%s";' % (name, str(value)) |
| 301 | |
| 302 | # Match Fields |
| 303 | for name, value in matchFields.items(): |
| 304 | cmd += 'te.match["%s"]="%s";' % (name, str(value)) |
| 305 | |
Daniele Moro | bef0c7e | 2022-02-16 17:47:13 -0800 | [diff] [blame] | 306 | if priority: |
| 307 | cmd += 'te.priority=%s;' % priority |
| 308 | |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 309 | response = self.__clearSendAndExpect(cmd) |
| 310 | if "Unknown action" in response: |
| 311 | main.log.error("Unknown action: " + response) |
| 312 | return main.FALSE |
| 313 | if "AttributeError" in response: |
| 314 | main.log.error("Wrong action: " + response) |
| 315 | return main.FALSE |
| 316 | if "Invalid value" in response: |
| 317 | main.log.error("Invalid action value: " + response) |
| 318 | return main.FALSE |
| 319 | if "Action parameter value must be a string" in response: |
| 320 | main.log.error( |
| 321 | "Action parameter value must be a string: " + response) |
| 322 | return main.FALSE |
| 323 | if "table" in response and "does not exist" in response: |
| 324 | main.log.error("Unknown table: " + response) |
| 325 | return main.FALSE |
| 326 | if "not a valid match field name" in response: |
| 327 | main.log.error("Invalid match field name: " + response) |
| 328 | return main.FALSE |
| 329 | if "is not a valid" in response: |
| 330 | main.log.error("Invalid match field: " + response) |
| 331 | return main.FALSE |
| 332 | if "Traceback" in response: |
| 333 | main.log.error("Error in creating the table entry: " + response) |
| 334 | return main.FALSE |
| 335 | return main.TRUE |
| 336 | except pexpect.TIMEOUT: |
| 337 | main.log.exception(self.name + ": Command timed out") |
| 338 | return main.FALSE |
| 339 | except pexpect.EOF: |
| 340 | main.log.exception(self.name + ": connection closed.") |
| 341 | main.cleanAndExit() |
| 342 | except Exception: |
| 343 | main.log.exception(self.name + ": Uncaught exception!") |
| 344 | main.cleanAndExit() |
| 345 | |
Daniele Moro | b8404e8 | 2022-02-25 00:17:28 +0100 | [diff] [blame] | 346 | def buildP4RtMeterEntry(self, meterName, index, cir=None, cburst=None, pir=None, |
| 347 | pburst=None): |
| 348 | # TODO: improve error checking |
| 349 | try: |
| 350 | main.log.debug( |
| 351 | "%s: Building P4RT Meter Entry (meter=%s, index=%d, cir=%s, " |
| 352 | "cburst=%s, pir=%s, pburst=%s)" % ( |
| 353 | self.name, meterName, index, str(cir), str(cburst), str(pir), str(pburst) |
| 354 | ) |
| 355 | ) |
| 356 | cmd = 'me = meter_entry["%s"]; ' % meterName |
| 357 | cmd += 'me.index=%d; ' % index |
| 358 | if cir is not None: |
| 359 | cmd += 'me.cir=%d; ' % cir |
| 360 | if cburst is not None: |
| 361 | cmd += 'me.cburst=%d; ' % cburst |
| 362 | if pir is not None: |
| 363 | cmd += 'me.pir=%d; ' % pir |
| 364 | if pburst is not None: |
| 365 | cmd += 'me.pburst=%d; ' % pburst |
| 366 | |
| 367 | response = self.__clearSendAndExpect(cmd) |
| 368 | if "meter" in response and "does not exist" in response: |
| 369 | main.log.error("Unknown meter: " + response) |
| 370 | return main.FALSE |
| 371 | if "UNIMPLEMENTED" in response: |
| 372 | main.log.error("Error in creating the meter entry: " + response) |
| 373 | return main.FALSE |
| 374 | if "Traceback" in response: |
| 375 | main.log.error("Error in creating the meter entry: " + response) |
| 376 | return main.FALSE |
| 377 | return main.TRUE |
| 378 | except pexpect.TIMEOUT: |
| 379 | main.log.exception(self.name + ": Command timed out") |
| 380 | return main.FALSE |
| 381 | except pexpect.EOF: |
| 382 | main.log.exception(self.name + ": connection closed.") |
| 383 | main.cleanAndExit() |
| 384 | except Exception: |
| 385 | main.log.exception(self.name + ": Uncaught exception!") |
| 386 | main.cleanAndExit() |
| 387 | |
Daniele Moro | 954e228 | 2021-09-22 17:32:03 +0200 | [diff] [blame] | 388 | def readNumberTableEntries(self, tableName): |
| 389 | """ |
| 390 | Read table entries and return the number of entries present in a table. |
| 391 | |
| 392 | :param tableName: Name of table to read from |
| 393 | :return: Number of entries, |
| 394 | """ |
| 395 | try: |
Daniele Moro | b8404e8 | 2022-02-25 00:17:28 +0100 | [diff] [blame] | 396 | main.log.debug( |
| 397 | self.name + ": Reading table entries from " + tableName) |
Daniele Moro | 954e228 | 2021-09-22 17:32:03 +0200 | [diff] [blame] | 398 | cmd = 'table_entry["%s"].read(lambda te: print(te))' % tableName |
| 399 | response = self.__clearSendAndExpect(cmd, clearBufferAfter=True) |
| 400 | # Every table entries starts with "table_id: [P4RT obj ID] ("[tableName]")" |
| 401 | return response.count("table_id") |
| 402 | except pexpect.TIMEOUT: |
| 403 | main.log.exception(self.name + ": Command timed out") |
| 404 | return main.FALSE |
| 405 | except pexpect.EOF: |
| 406 | main.log.exception(self.name + ": connection closed.") |
| 407 | main.cleanAndExit() |
| 408 | except Exception: |
| 409 | main.log.exception(self.name + ": Uncaught exception!") |
| 410 | main.cleanAndExit() |
| 411 | |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 412 | def disconnect(self): |
| 413 | """ |
| 414 | Called at the end of the test to stop the p4rt CLI component and |
| 415 | disconnect the handle. |
| 416 | """ |
| 417 | response = main.TRUE |
| 418 | try: |
| 419 | if self.handle: |
| 420 | self.handle.sendline("") |
| 421 | i = self.handle.expect([self.p4rtShPrompt, pexpect.TIMEOUT], |
| 422 | timeout=2) |
| 423 | if i != 1: |
| 424 | # If the p4rtShell is still connected make sure to |
| 425 | # disconnect it before |
| 426 | self.stopP4RtClient() |
| 427 | i = self.handle.expect([self.prompt, pexpect.TIMEOUT], |
| 428 | timeout=2) |
| 429 | if i == 1: |
| 430 | main.log.warn( |
| 431 | self.name + ": timeout when waiting for response") |
| 432 | main.log.warn( |
| 433 | self.name + ": response: " + str(self.handle.before)) |
| 434 | self.handle.sendline("exit") |
| 435 | i = self.handle.expect(["closed", pexpect.TIMEOUT], timeout=2) |
| 436 | if i == 1: |
| 437 | main.log.warn( |
| 438 | self.name + ": timeout when waiting for response") |
| 439 | main.log.warn( |
| 440 | self.name + ": response: " + str(self.handle.before)) |
| 441 | return main.TRUE |
| 442 | except TypeError: |
| 443 | main.log.exception(self.name + ": Object not as expected") |
| 444 | response = main.FALSE |
| 445 | except pexpect.EOF: |
| 446 | main.log.error(self.name + ": EOF exception found") |
| 447 | main.log.error(self.name + ": " + self.handle.before) |
| 448 | except ValueError: |
| 449 | main.log.exception("Exception in disconnect of " + self.name) |
| 450 | response = main.TRUE |
| 451 | except Exception: |
| 452 | main.log.exception(self.name + ": Connection failed to the host") |
| 453 | response = main.FALSE |
| 454 | return response |
| 455 | |
Daniele Moro | 954e228 | 2021-09-22 17:32:03 +0200 | [diff] [blame] | 456 | def __clearSendAndExpect(self, cmd, clearBufferAfter=False, debug=False): |
| 457 | self.clearBuffer(debug) |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 458 | self.handle.sendline(cmd) |
| 459 | self.handle.expect(self.p4rtShPrompt) |
Daniele Moro | 954e228 | 2021-09-22 17:32:03 +0200 | [diff] [blame] | 460 | if clearBufferAfter: |
| 461 | return self.clearBuffer() |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 462 | return self.handle.before |
| 463 | |
| 464 | def clearBuffer(self, debug=False): |
| 465 | """ |
| 466 | Keep reading from buffer until it's empty |
| 467 | """ |
| 468 | i = 0 |
| 469 | response = '' |
| 470 | while True: |
| 471 | try: |
| 472 | i += 1 |
| 473 | # clear buffer |
| 474 | if debug: |
| 475 | main.log.warn("%s expect loop iteration" % i) |
Jon Hall | 376f503 | 2022-01-27 16:16:29 -0800 | [diff] [blame] | 476 | self.handle.expect(self.p4rtShPrompt, timeout=2) |
Daniele Moro | c9b4afe | 2021-08-26 18:07:01 +0200 | [diff] [blame] | 477 | response += self.cleanOutput(self.handle.before, debug) |
| 478 | except pexpect.TIMEOUT: |
| 479 | return response |