Background
We have a handful of Cisco C40s that I’m trying to automate via bash (although I’d be open to alternatives). I need to log into it, dial an IP, get back the call ID that’s returned, and then use that CallID to send a DTMF tone to the far end. I’m able to get about 90% of the way there but for some reason using SSH isn’t returning all the text that’s returned when using an interactive session.
Interactive Shell Sample:
login as: admin Using keyboard-interactive authentication. Password: Welcome to XXX TANDBERG Codec Release TC7.1.1.168aadf SW Release Date: 2014-04-11 *r Login successful OK xConfiguration Audio Volume: 0 ** end OK xCommand Dial Number: FAR_END_IP OK *r DialResult (status=OK): CallId: 73 ConferenceId: 44 ** end
Non-Interactive Shell Samples
Without ssh -T
or ssh -t -t
Options
This occurs where call-init-step1.txt contains the xConfiguration Audio Volume: 0
and xCommand Dial Number: FAR_END_IP
in addition to a bye
to hang up.
[user@controlserver C40]$ cat call-init-step1.txt | ssh admin@cisco_codec Pseudo-terminal will not be allocated because stdin is not a terminal. Welcome to XXX TANDBERG Codec Release TC7.1.1.168aadf SW Release Date: 2014-04-11 *r Login successful OK ** end OK OK
What I’m missing here is the block with
*r DialResult (status=OK): CallId: 73 ConferenceId: 44 ** end
so that I can parse it for the CallID
and then use it to send the next commands.
With ssh -T
or ssh -t -t
Options
A few threads had suggested using ssh -T
or ssh -t -t
which in this case doesn’t seem to help, below is the output.
[user@controlserver C40]$ cat call-init-step1.txt | ssh -T admin@cisco_codec Welcome to XXX TANDBERG Codec Release TC7.1.1.168aadf SW Release Date: 2014-04-11 *r Login successful OK ** end OK OK
And
[user@controlserver C40]$ cat call-init-step1.txt | ssh -t -t admin@cisco_codec Welcome to XXX TANDBERG Codec Release TC7.1.1.168aadf SW Release Date: 2014-04-11 *r Login successful OK ** end OK OK
The Question
Any insight in how to get the missing DialResult
block would be greatly appreciated.
Edit: I should also mention that ultimately the command cat call-init-step1.txt | ssh admin@cisco_codec
would get redirected to a file to be parsed further on in my script meaning it would probably look something like cat call-init-step1.txt | ssh -t -t admin@cisco_codec > results.txt
and then be parsed.
Edit 2: To frame what I’m working with the full API guide is found here
Edit 2.5: An attempt with expect
Per a suggestion from @MarkSetchell we went ahead and wrote a semi-functional expect script that looks like the following:
#!/usr/bin/expect spawn ssh admin@cisco_codec expect "*r Login successful" send "xConfiguration Audio Volume: 0" expect "OK" send "xCommand Dial Number: FAR_END_IP" expect "** end"
That resulted in the following:
[user@controlserver C40]$ expect expect-call spawn ssh admin@cisco_codec Welcome to XXX TANDBERG Codec Release TC7.1.1.168aadf SW Release Date: 2014-04-11 *r Login successful OK xConfiguration Audio Volume: 0xCommand Dial Number: FAR_END_IPxConfiguration Audio Volume: 0xCommand Dial Number: FAR_END_IP
Advertisement
Answer
It seemed there were a few approaches to this, if I had to stay with bash it seemed that using a properly written except
script was going to be the way to go. That said someone on a Reddit post I’d made suggested XML. Using XML had crossed my mind but neither I nor the folks I immediately had to help me were super well versed with how to go about this but with an incredibly helpful post I was well on my way to using Python and XML.
The final product ended up looking like something like this:
#!/usr/bin/env python import time import requests from lxml.etree import fromstring, Element, tostring def putxml_request(xml, **kwargs): return requests.post( 'http://HOSTNAME/putxml', auth=('USER', 'PASSWORD'), data=xml.format(**kwargs)).content def xconfiguration_request(*path, **keys_and_values): root = Element('Configuration') parent = root for level in path: current = Element(level) parent.append(current) parent = current for k, v in keys_and_values.iteritems(): node = Element(k) node.text = str(v) current.append(node) xml = tostring(root, pretty_print=True) return putxml_request(xml) xconfiguration_request('Audio', Volume=0) DIAL = ''' <Command> <Dial> <Number>{number}</Number> <Protocol>{protocol}</Protocol> </Dial> </Command>''' outcome = putxml_request( DIAL, number='XXX', protocol='Sip') callid = fromstring(outcome).xpath('//CallId')[0].text # this gives it some time for the call to connect time.sleep(10) DTMFSEND = ''' <Command> <DTMFSend> <CallId>{callid}</CallId> <DTMFString>{dtmf}</DTMFString> </DTMFSend> </Command>''' outcome = putxml_request(DTMFSEND, callid=callid, dtmf='1234') status = fromstring(outcome).xpath('//DTMFSendResult')[0].attrib['status'] if status != 'OK': print('bad') else: print('sent dtmf')
Ultimately I ended up scheduling this script to initiate a call (via cron) and then wrote a very similar script to hang up the call using DisconnectAll
.
I hope this helps someone and thanks to /u/omgdave on Reddit (who I’d offered an opportunity to respond to this with an answer but wasn’t taken up on it) for the help with this.