3 years ago
Cisco IOS-XR backups
Hello,
It does not look like there is a Cisco IOS-XR config module to add for IOS-XR device backups. Maybe I'm missing something? Is there a way to do this with just groovy script maybe? A...
Data Collection
/*******************************************************************************
* © 2007-2019 - LogicMonitor, Inc. All rights reserved.
******************************************************************************/
import com.santaba.agent.groovyapi.expect.Expect
def host = hostProps.get("system.hostname")
def teln = hostProps.get("configsource.use.telnet").toString().toBoolean()
def user = hostProps.get("ssh.user") ?: hostProps.get("config.user")
def pass = hostProps.get("ssh.pass") ?: hostProps.get("config.pass")
def epas = hostProps.get("ssh.enable.pass") ?: hostProps.get("config.enable.pass") ?: pass
def admn = hostProps.get("ssh.admin") ?: "false"
def port = teln ? (hostProps.get("configsource.telnet.port") ?: 23) : (hostProps.get("ssh.port") ?: 22)
def wild = instanceProps.get("wildvalue")
// Basic mode is used for devices that can't handle terminal or enable commands.
def basicMode = hostProps.get("system.sysinfo").contains("Cisco Firepower Threat Defense")
def cli
def timeout = 60
def success = false
def error = ""
// We remove common characters used to decorate a terminal.
def termClean = /(.?\[\?7h)/
def rawPrompt = /(?-m)[^\n\s]*[>#$]\s*$/
// CLI based approaches fail often. We make up to 10 attempts to connect and retrieve a single byte of data.
def retries = 0
while(retries < 10 && !success) {
try {
cli = teln ? Expect.open(host, port.toInteger(), timeout) : Expect.open(host, port.toInteger(), user, pass, timeout)
cli.expect(".")
success = true
}
catch (ex) {
sleep(1000)
error += "[Connection Attempt ${retries}] ${ex.message}\n"
}
retries++
}
if(success) {
try {
if(teln) {
// Telnet sometimes doesn't require a user to be sent. Only do so if requested.
cli.expect(["[Uu]?sername:", "[Pp]?assword:"] as String[])
if(cli.matched().toLowerCase().contains("name")) {
cli.send("${user}\n")
cli.expect("[Pp]?assword:")
}
cli.send("${pass}\n")
}
// At this point we are ready to check for a prompt, but don't know if the device has finished sending data.
// We wait until the data has been stable for 100 ms. Staggered/Delayed data is reasonably common.
def outputStability = 0
def lastLength = -1
while(outputStability < 100) {
outputStability += (cli.stdout().length() == lastLength) ? 1 : -outputStability
lastLength = cli.stdout().length()
sleep(10)
}
// Now the data is stable, the standard output should have everything we need to gather a prompt.
def stdPrompt = cli.stdout() =~ rawPrompt.bitwiseNegate()
def prompt = ""
if(stdPrompt.size()) {
// We've got our best guess at a prompt at this point. Clean it and get it ready for line matching.
prompt = "^${termClean}*.?${stdPrompt[0].trim().replaceAll(termClean,'').drop(1).trim().replaceAll(/[.*+?^()|\[\]\\{}$]/, '\\\\$0')}"
// Consume the buffer expecting the prompt so we know we have a clean buffer to work with for future matches.
cli.expect(prompt)
}
else {
// We didn't find a prompt in the standard output. Use expect to wait for one.
cli.expect(rawPrompt)
prompt = "^.?${cli.matched().replaceAll(termClean,'').trim().replaceAll(/[.*+?^()|\[\]\\{}$]/, '\\\\$0')}"
}
if(!basicMode) {
// The enable command will elevate the prompt if possible.
cli.send("enable\n")
cli.expect(["(?i)password:", "${prompt[0..-2]}[>#\$]", prompt] as String[])
if(cli.matched().toLowerCase().contains("password")) {
cli.send("${epas}\n")
cli.expect(rawPrompt)
}
// The enable command may have changed the prompt. We've got to get it again.
prompt = "^${cli.matched().replaceAll(termClean,'').trim().replaceAll(/[.*+?^()|\[\]\\{}$]/, '\\\\$0')}"
// We aren't in basic mode so try and configure the terminal to avoid pagination with both variations of the width and length commands.
["width", "length"].each { dimension ->
cli.send("terminal ${dimension} 0\n")
cli.expect(prompt)
if(cli.before().trim().readLines().size() > 1) {
cli.send("screen-${dimension} 0\n")
cli.expect(prompt)
}
}
if(admn.toLowerCase().contains("true")) {
// Attempt to enter admin mode if requested.
cli.send("admin\n")
cli.expect(rawPrompt)
prompt = "^.?${cli.matched().replaceAll(termClean,'').trim().replaceAll(/[.*+?^()|\[\]\\{}$]/, '\\\\$0')}"
}
}
// Execute the show command for this instance, adding a space for each page we may need to postpone a prompt being returned.
def maxPages = 50
cli.send("show ${wild}${' ' * maxPages}\n")
// As we sometimes need to paginate the output, we will concatenate the output in a buffer.
def buffer = ""
// We loop throught all potential pages up to the limit set by "maxPages" (a default of 50 was double the ammount needed for anything in testing).
for(int i = 0; i < maxPages; i++) {
cli.expect([prompt, "\\s?--More--\\s?"] as String[])
// Attempt to clean the output of unwanted information, specifically backspace characters and the --More-- decoration.
buffer += cli.before().replaceAll("(\b+\\s*\b+)|(\\s?--More--\\s?)","")
if(cli.matched().contains("--More--")) {
// We hit a page break. Send many new line characters to greatly reduce the chance of an early prompt being returned (one is not reliable).
cli.send("${' ' * maxPages}\n")
} else {
// We matched the prompt and have to assume we've gotten everything. We are unable to continue pagination even with more new line characters.
break
}
}
def output = buffer.trim()
// Print the output of the show command if the input was valid. Remove the last line (the prompt).
if(!output.toLowerCase().contains("invalid input detected")) {
println output.readLines().drop(1).join('\n').trim()
}
} catch (ex) {
error += "[Collecting] ${ex.message}\n"
success = false
}
try {
// Add an extra exit if we might be in privilege mode.
cli.send("exit\n" * (basicMode ? 2 : 1))
cli.close()
}
catch(ex) {
error += "[Closing] ${ex.message}\n"
success = false
}
}
if(!success) {
error += cli ? "--- CLI Output ---\n${cli.stdout()}\n" : ""
error += cli ? "--- CLI Base64 ---\n${cli.stdout().bytes.encodeBase64().toString()}\n" : ""
println "--- Error Logs ---\n${error}"
new File("../logs/Cisco_IOS_Mini_Collection ${host.replaceAll('[^a-zA-Z0-9-_\\.]', '_')}.txt").write(error)
return 1
}
return 0