Python Getting Started Guide
Python Getting Started Guide
Abstract: This document provides a quick tutorial for installing and setting up Python, introduces
Python scripting, and discusses Python application creation, showing examples with both scripting
and daemon.
Published: February 2015
Contents
Disclaimer ..................................................................................................................................4
Introduction ..............................................................................................................................4
References .................................................................................................................................................... 4
Mac OS X ....................................................................................................................................................... 8
Ubuntu ........................................................................................................................................................... 8
Choosing an IDE.......................................................................................................................................... 8
Future .........................................................................................................................................46
EXOS SDK .................................................................................................................................47
Disclaimer
Scripts can be found on Extremes website and are provided free of charge by Extreme. We hope
such scripts are helpful when used in conjunction with Extreme products and technology; however,
scripts are provided simply as an accommodation and are not supported nor maintained by
Extreme.
ANY SCRIPTS PROVIDED BY EXTREME ARE HEREBY PROVIDED "AS IS", WITHOUT WARRANTY
OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL EXTREME OR ITS THIRD PARTY LICENSORS BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE USE OR DISTRIBUTION OF SUCH SCRIPTS.
Introduction
Python is a very popular programming language and is gaining a lot of traction in the switching
industry as well. Python version 2.7.3 has been integrated into EXOS since EXOS 15.6.1.
This document will detail installing a working Python environment on most laptop systems,
including installing Python and its necessary tools to an IDE. This guide will try to present the very
basics of Python language, for those who are new to the language. The aim of this guide is not to
teach Python, and readers are strongly encouraged to consult existing resources.
If you are already familiar with Python, then you should skip the teaching sections and go directly to
the EXOS CLI Modules section.
References
The following documents were used extensively in the preparation of this document:
https://docs.python.org/2/
Installing Python
Windows
1.
Download the windows MSI installer for the latest 2.7 version from the Python website:
https://www.python.org/downloads/
NOTE
At the time of this writing, the 2.7.9 version is available at:
https://www.python.org/downloads/release/python-279/
2. Follow the Python installer wizard, leaving the default directory (C:\Python27)
unchanged for now.
3. When Python installation is complete, modify your computers Path environment
variable so that Python can be called from anywhere in the directory. To do this:
a. From your computers System Properties, (Control Panel > System > Advanced
System Settings), click the Advanced tab.
b.
4. Once Python is installed, invoke the Python interpreter from a command shell, and run
any program from it.
Before we can begin writing Python code, well first configure the system to easily install libraries
using easy_install and pip.
Installing pip
Well use easy_install to install pip. From the Python directory, execute the command
easy_install pip.
Mac OS X
Installation on Mac OS X is more straightforward than on Windows. Simply download and install the
latest 2.7 .dmg installer from the Python website (https://www.python.org/downloads/mac-osx/)
Once Python is installed, ensure correct setup by opening a terminal and executing the easy_install
program. Unlike Windows, easy_install is part of the installation of Python on Mac.
In your console, type the following command: sudo easy_install pip.
Ubuntu
Python is everywhere in Linux, and as such, installation is the simplest one: Python is already
installed! However it is possible that Python 2.7 is not part of touch images of Ubuntu, replaced by
Python 3. https://wiki.ubuntu.com/Python/3
With Ubuntu 14.10, python 2.7.8 is integrated by default, along with Python 3.4.2. Python 2.7 is still
part of the archive, and should be until 2020, which is the targeted EOL of Python 2.
Depending on your version, you may have to install/upgrade Python 2.7. As both version are
available, youll need to specify which one to use in your script, with the usual following line at the
start of your script.
#!/usr/bin/python
Choosing an IDE
Included with Python, you can find the IDLE GUI IDE. You can use it for small programs, but it is
recommended to find a more complete IDE to ease development.
Choosing an IDE is a matter of taste, so you are encouraged to find the one that suits you best. Here
are a few options:
Eclipse: available on every system and very common on Linux and Mac OS X.
Visual Studio Express: the free version of Microsoft program is very complete and
powerful (http://www.visualstudio.com/en-us/products/free-developer-offers-vs).
Wing IDE 101: the free version of this IDE is very simple to use. This is the one well use
through this document (http://wingware.com/downloads/wingide-101).
Vim: Vi Improved is also a good IDE under Unix environment and is part of Ubuntu
installation.
Python is a more widely used programming tool and is more powerful (with strings,
manipulation, etc.).
Python is modern programming tool that universities are teaching to their students and
used in Networking/Datacenter environment (Google, Facebook, etc.).
A lot of code and libraries already exist and can be reused easily.
Basic Python
For those who dont know Python, this section provides a quick presentation of some common,
basic concepts.
Using your IDE, type the code and execute it. The result should be displayed inside the Python Shell
window integrated.
The image above shows the typical Wing IDE GUI. You can write your Python code in the main
window (1), where several tabs can be opened for several files. For testing the result of your code,
you can execute the code of the active window (2) and see the result in the Python Shell window
(3).
Variables
Python doesnt require having a variable type definition; it figures it out. You can simply declare it
and affect a value without any other burden. Within the Python interpreter, you can see the value of
a variable by simply typing its name.
>>> a = 5
>>> a
5
We can do all the math we want on integer or float type variable. We can also add strings together
easily:
>>>
>>>
>>>
>>>
47
a = 5
b = 42
a = a+b
a
>>> a = 5
>>> a += 1
>>> a
6
>>>
>>>
>>>
>>>
42
>>>
5
a = 5
b = 42
a,b = b,a
a
b
>>> s1
>>> s2
>>> s1
>>> s1
Hello
= Hello
= Extreme!
= s1+ +s2
Extreme!
The int type means integer. This is a number, without a decimal. The str type means string. This
is a character string.
10
You can convert a type to another using the function with the same names:
a = 5
s = "Hello Extreme!"
print a,type(a)
print s,type(s)
a = str(a)
print a,type(a)
a = int(a)
print a,type(a)
Print
To print something to the screen (the terminal), we use the built-in function print. We can use it
differently to achieve the same result.
The following simple script is an example:
a = 5
s = "Hello Extreme!"
print 'The value of a is',a,'and the value of s is:',s
In the second example in the above code, if we swap the value in curly braces, we see that we
change the order of the print. It would have been similar if wed changed the order in the
format() function:
>>> print "The value of a is {1} and the value of s is: {0}".format(a,s)
The value of a is Hello Extreme! and the value of s is: 5
11
Indentation is critical because unlike most of other programming languages, this is not just for ease
of reading, but necessary for Python to understand the instruction blocks.
Heres a simple example:
a = 42
if a > 0:
print 'a is positive'
elif a < 0:
print 'a is negative'
else:
print 'a is null'
While
You can also use the while loop condition.
i = 0
while i < 10:
print i,'*',i,'=',i*i
i += 1
For
Another very useful loop condition is the for loop. If you have already done some coding in C or
similar languages,, you will notice that this instruction is different in Python. Its close to the
foreach instruction in Perl or PHP.
The for instruction works on a sequence. It can be some list, like a character string. For example:
for element in sequence:
The element variable is created by the for instruction. You dont have to create it. It takes the
value of each successive entry in the sequence.
Here is a simple example:
s = "Hello Extreme!"
for letter in s:
print letter
12
Try
You can define a block of program within a try statement. The block tries to execute, but if theres
a problem, like an exception, it will jump to the except statement that matches the exception
(error type).
You can have several except statements, one for each error type that could be returned and/or an
except statement without any error type that would catch any error. Its also possible to have
several error types for a same except statement.
The finally statement is a way to execute the code it contains whatever happened. This try
statement is a nice way to handle interruption or error (like a wrong argument passed or not
enough):
>>> def foo():
...
try:
...
1/0
...
finally:
...
return 42
...
>>> foo()
42
With
The with statement, introduced in Python 2.5, is a nice way to use an expression, execute a block
of program, and exit cleanly, because the with statement takes care of everything. This is very
helfpul for file management.
def readStatus():
try:
with open(STATUS_FILE,'r') as fd:
status = fd.read()
except:
status = 0
try:
return int(status)
except:
return 0
The file opened in the with statement is automatically closed when the second try is reached.
13
Creating Functions
As the examples in the previous section show, we can create functions to make more complex
programs. A new function is defined with the keyword def followed by the name of the function,
two brackets ( < > ) with an optional list of arguments, and the semi-colon.
Respecting the indentation necessary for Python to execute properly, we can then enter our code
for that function. At the end of the block, we can return a value, using the keyword return.
def foo():
#<instructions> note that this is a comment
return 42
Functions can be made more complex by using multiple arguments of different types, and an
undefined number of optional arguments. Likewise, you can return several values of different type
as well as lists of arguments (tuples).
>>> def foo(a,b):
...
for letter in b:
...
print letter
...
a = a*a
...
b = b+" Extreme!"
...
return a,b
...
>>> foo(42,"Hello")
H
e
l
l
o
(1764, 'Hello Extreme!')
You can also have arguments with a predefined value. This way, if the arguments are not passed,
they will have a default value.
Heres an example using the previous script but it omits sending a string to the foo() function. As
you can see, the definition of the foo() function has a predefined value.
>>> def foo(a,b="Hello"):
...
for letter in b:
...
print letter
...
a = a*a
...
b = b+" Extreme!"
...
return a,b
...
... def main():
...
print foo(42)
...
>>> main()
H
e
l
l
o
(1764, 'Hello Extreme!')
14
Arguments
Variable Arguments
We already saw that we can send arguments to a function, but what if we want a variable number
of arguments? Hopefully, we can define such a function. Its also possible to mix the function with
mandatory arguments, as long as the mandatory ones are passed first.
>>> def manyArgs(*arg):
... print "I was called with", len(arg), "arguments:", arg
...
>>> manyArgs("Extreme")
I was called with 1 arguments: ('Extreme',)
>>> manyArgs(42,"Hello", "Extreme", 5)
I was called with 4 arguments: (42, 'Hello', 'Extreme', 5)
System Arguments
When creating a script/program in Python, we may need to receive some arguments from the
command line. These arguments can be flags to format the expected output, or some parameters
needed for treatment in the program.
The basic way to catch these arguments is to use the system module. You must first import that
module (using the keyword import) so we can use its functions. Each argument passed in the
command line is listed in the sys.argv list. Heres an example:
import sys
def main():
i = 0
while i < len(sys.argv):
print sys.argv[i]
i += 1
if __name__ == '__main__':
try:
main()
except SystemExit:
pass
15
Looking ahead to the next section slightly, we can run that script (named test.py) on an Extreme
switch running EXOS 15.6.1, we get the following output:
X440-8t.5
exsh
X440-8t.6
X440-8t.6
exsh
5
X440-8t.7
exsh
5
42
Hello
X440-8t.8
But Python offers more than that. Using the argparse module, we have a better tool to handle
arguments and can offer a built-in help mode.
With argparse, its possible to list all the possible arguments, generate a help message, and define
which are mandatory.
import argparse
def getParams():
parser = argparse.ArgumentParser(prog=param)
parser.add_argument('-i','--remote_address',
help='Remote IP address to ping',
action='append',
required=True)
parser.add_argument('-p','--policy',
help='Name of ACL policy',
required=True)
parser.add_argument('-f','--from_address',
help="'From' IP address in ping command")
parser.add_argument('-r','--virtual_router',
help='Virtual Router used for ping')
args = parser.parse_args()
return args
A simple script (named param.py) calling this function returns the following output if we do not
pass any argument:
X440-8t.6 # run script param
usage: param [-h] -i REMOTE_ADDRESS -p POLICY [-f FROM_ADDRESS]
[-r VIRTUAL_ROUTER]
param: error: argument -i/--remote_address is required
X440-8t.7 #
16
If we pass the valid h argument, we have a nice help output. The text used in this help message is
the one defined in each help parameter in the code.
X440-8t.10 # run script param -h
usage: param [-h] -i REMOTE_ADDRESS -p POLICY [-f FROM_ADDRESS]
[-r VIRTUAL_ROUTER]
optional arguments:
-h, --help
show this help message and exit
-i REMOTE_ADDRESS, --remote_address REMOTE_ADDRESS
Remote IP address to ping
-p POLICY, --policy POLICY
Name of ACL policy
-f FROM_ADDRESS, --from_address FROM_ADDRESS
'From' IP address in ping command
-r VIRTUAL_ROUTER, --virtual_router VIRTUAL_ROUTER
Virtual Router used for ping
X440-8t.11 #
Finally, it is important to note that the arguments can be passed in any order.
User Input
To catch user input in a program, we can use the raw_input() function. It reads a line from input,
converts it to a string value, and returns that. Heres an example:
>>> s = raw_input('Enter a sentence: ')
Enter a sentence: Hello Extreme!
>>> s
'Hello Extreme!'
Standard Library
The Python implementation in EXOS uses the standard library, but not all the library is included for
size reasons. Many more modules are available, and what they do and how they work are provided
in the online documentation at: https://docs.python.org/2/library/index.html.
Among the different modules you may need the most, the following should be the most common:
os
sys
socket
io
argparse
17
Using socket, you can create a network socket to establish a communication between devices. The
socket module supports IPv4, IPv6, UDP, TCP and UDS for the most popular ones.
The io module is related to file management and anything else related to typical input/output
mechanism.
The argparse module is described in the System Arguments section.
Exsh Module
Like every other modules described previously, EXOS CLI has a Python module ready. To start using
it, we have to import it. This module is called exsh.
What this module offers is the ability to send a CLI command to EXOS, and capture the result. We
can work either in xml, text, both or none.
Heres the description of this module:
exsh.clicmd(cmd, capture=False, xml=False, args=None)
Send a CLI command to EXOS.
Parameters:
cmd (str) any valid EXOS CLI command
capture (True or False) if capture = True, CLI output text is returned
xml (True or False) if xml = True, the XML that EXOS used to create the CLI output is
returned
args (str) used to provide additional input to some EXOS commands that prompt
for more information
Returns:
None if both capture and xml are False.
Captured text if capture is True.
XML if xml is True.
Captured text and XML if both capture and xml are True.
Raises:
RuntimeError EXOS command is invalid or encountered an error
The display text may change from release to release which makes it more difficult to
maintain a script that parses CLI responses from one release to another. Using the XML is a
more machine-readable method of processing the CLI output. The XML tends to be stable
from one release to another and is not subject to formatting changes.
18
19
Exshexpect Module
It may be desirable to interact with EXOS using expect scripting functionality. The Python
community offers a pexpect.py module that can provide this capability. The home page for
pexpect can be found at: http://pexpect.readthedocs.org/en/latest/
EXOS uses pexpect.py version 3.2. Documentation can be found at:
https://pypi.python.org/pypi/pexpect/
The pexpect.py module provides interfaces to run interactive shell commands and to process the
results through expect like functions. The challenge with exsh.clicmd() is that it is a
synchronous call and not a separate process.
The exshexpect.py module was developed to provide a wrapper for pexpect.py that
interfaces to exsh.clicmd().
The following is an example of using pexpect together with the exshexpect module:
import pexpect
import exshexpect
exosPrompt = '::--:--::' # <- make up a prompt for expect that will not
match any command output.
p = exshexpect.exshspawn(exosPrompt, exsh.clicmd) # <- create an expect
object. Pass in the prompt and the back end function to call
p.sendline('show fan')
this case exsh.clicmd)
Special case for passing in a command with an additional argument described in exsh.clicmd():
p.send('enable debug-mode\nC211-6BB4-E6F5-B579\n')
In the line above, use send() and include \n terminations for the command and the additional
parameter. This is the same as calling:
exsh.clicmd('enable debug-mode', capture=True, args=C211-6BB4E6F5-B579)
but using expect to process any response.
idx = p.expect([exosPrompt, pexpect.EOF, pexpect.TIMEOUT])
20
The try/except statement is necessary as the /proc recommended method to set the VR ID is
known to have a Permission Error with at least EXOS 15.6.1, 15.6.2, and 15.7.1. For future compatibility,
it is then recommended to follow this way of coding.
The value in the f.write() function, as the last parameter in the setsockopt() function, sets
the desired VR we want to use for the socket.
You can find the numeric VR ID for any Virtual Router name on a system using the following script:
def vrNameToNumber(vrName):
vrId = None
with open('/proc/net/vr','r') as f:
for l in f.readlines():
rslt = l.split()
if rslt[3] == vrName:
vrId = int(rslt[0])
break
return vrId
By printing the the result of this function, you can find the numeric VR ID for any VR name:
VM1.3 # vi findVR.py
def vrNameToNumber(vrName):
vrId = None
with open('/proc/net/vr','r') as f:
for l in f.readlines():
rslt = l.split()
if rslt[3] == vrName:
vrId = int(rslt[0])
break
return vrId
print
print
print
print
~
~
vrNameToNumber('VR-Mgmt')
vrNameToNumber('VR-Default')
vrNameToNumber('VR-Control')
vrNameToNumber('VR-Unknown')
21
Execution
A Python script is a text file with a .py extension. Starting with EXOS 15.6.1, such a file is recognized
by EXOS. It is possible to execute it manually so that it runs to completion, with several commands:
The existing EXOS command for TCL, load script <script>, has been enhanced
to support Python script.
22
In this example, the ping.py Python script requires various parameters, and can be executed
several times to provide its services to different resources. UPM also offers the advantage to pass
environment variables that can be used in a script. This turns a static script into a dynamic script
that can be generic enough to work on various cases.
Extreme Networks, Inc. All rights reserved.
23
With TCL, the UPM environment variables are embedded. With Python scripting these variables
have to be passed as arguments.
Below is a simple script (lldp-device.py) detecting that a LLDP device has been connected (or
removed) on a switch.
#!/usr/bin/python
import exsh
import sys
def main():
voiceVLAN = 'ToIP'
port = sys.argv[2]
if sys.argv[1] == 'DEVICE-DETECT':
exsh.clicmd('create log entry \"LLDP device detected on port
{}\"'.format(port))
exsh.clicmd('config vlan {} add port {} tagged'.format(voiceVLAN,
port))
exsh.clicmd('config lldp port {} advertise systemname'.format(port))
exsh.clicmd('config lldp port {} advertise vendor-specific dot3
mac-phy'.format(port))
exsh.clicmd('config lldp port {} advertise vendor-specific med
capabilities'.format(port))
exsh.clicmd('config lldp port {} advertise vendor-specific med
power-via-mdi'.format(port))
elif sys.argv[1] == 'DEVICE-UNDETECT':
exsh.clicmd('config vlan {} del port {}'.format(voiceVLAN, port))
exsh.clicmd('unconfig lldp port {}'.format(port))
exsh.clicmd('create log entry \"LLDP device removed from port
{}\"'.format(port))
if __name__ == '__main__':
try:
main()
except SystemExit:
pass
With a UPM profile configured to launch our Python script when a device is detected/undetected by
LLDP, we have a very dynamic script.
The voiceVLAN variable in our Python script could be another argument to make it even more
generic. We could also test if the VLAN already exists (and create it if needed), and we should first
validate the correct parameters have been entered if we wanted to productize this script.
X440-8p.6 # create upm profile lldp-detect
Start typing the profile and end with a . as the first and the only
character on a line.
Use - edit upm profile <name> - for block mode capability
run script lldp-device $EVENT.NAME $EVENT.USER_PORT
.
X440-8p.7 # configure upm event device-detect profile lldp-detect ports 18
Extreme Networks, Inc. All rights reserved.
24
When connecting an IP Phone on port 5 of a Summit x440-8p, running EXOS 15.6.1, we see the
following in the logs:
X440-8p.10 # sh log
01/14/2015 17:09:43.36 <Info:System.userComment> LLDP device detected on
port 5
01/14/2015 17:09:42.12 <Noti:UPM.Msg.upmMsgExshLaunch> Launched profile
lldp-detect for the event device-detect
01/14/2015 17:09:42.12 <Noti:UPM.Msg.LLDPDevDetected> LLDP Device
detected. Mac is 00:07:3B:D3:27:3E, IP is 0.0.0.0, on port 5, device type
is 5, max power is 5600
01/14/2015 17:09:10.38 <Noti:POE.port_delivering> Port 5 is delivering
power
01/14/2015 17:09:10.33 <Info:vlan.msgs.portLinkStateUp> Port 5 link UP at
speed 100 Mbps and full-duplex
A total of 5 log messages were displayed.
X440-8p.11 #
Theres no need to try to use the Python print function with UPM because the output is redirected
and nothing is displayed (neither in console nor in the active terminal).
25
Python Apps
With EXOS 15.7.1 onwards, the use of Python is not limited anymore to simply scripting. Its now
possible to create native applications written in Python that can be launched manually or
automatically and be persistent. These applications are executed as EXOS processes and are
managed by EPM. As such its possible to show, start, terminate, restart, etc. any process created.
The Python environment for Native Apps, the daemon one, is a different environment than the
scripting one presented previously. They both are based on Python 2.7, but each has a different set
of modules available. Over time, they should be less different.
Runtime Environment
Python applications are run in a Python container known as EXPY. Its a simple C-based EXOS
application that is linked against the Python runtime environment. It also implements the EXOS
Python API.
In most cases, EXPY will be transparent to a Python developer. However, if packaging an XMOD,
you will need to understand its parameters:
Usage: expy [OPTION] -c <SCRIPT>
or: expy [OPTION] -m <MODULE>
Run python code in a SCRIPT or MODULE.
-c
-m
-n <name>
-d
26
System Functions
On the system level, a few functions are available:
exos.api.force_exit(retcode)
Forcibly exit the Python container (expy).
Parameters:
retcode (int): code to return to the exit() system function.
Returns:
None.
exos.api.get_process_name()
Get the name of this process.
Returns:
Process_Name (str)
exos.api.set_process_name(process_name)
Set the name of this process.
Parameters:
process_name (str): name of the process
Returns:
None
Logging Functions
Trace buffers are circular in-memory log buffers and are typically used for collecting debug data,
which can then be displayed in the CLI or dumped to a file. A process can have multiple trace
buffers. It is typically convenient to use one for each component or library within a process.
class exos.api.TraceBuffer(buffer_name, buffer_size)
A circular in-memory log buffer
log(msg)
Log msg to the trace buffer
dump(file_name)
Dump this trace buffer to file_name. When file_name is None, the filename is generated.
The file is always written under /usr/local/tmp.
clear()
Clear this trace buffer. All existing log entries will be deleted.
Extreme Networks, Inc. All rights reserved.
27
emit(record)
Write the record to the trace buffer.
trace_buffer = None
The underlying trace buffer
exos.api.dump_trace_buffer(buffer_name, file_name)
Dump a trace buffer buffer_name to a file file_name. The trace buffer will be written to
/usr/local/tmp/<file_name>
Parameters:
buffer_name (str): if None, then all buffers will be dumped.
file_name (str): if None, the file name will be generated.
Raises:
UnknownTraceBufferError: the buffer_name does not exist.
exos.api.clear_trace_buffer(buffer_name)
Clear the trace buffer buffer_name.
Parameters:
Buffer_name (str): if None, then all buffers are cleared.
Raises:
UnknownTraceBufferError: the buffer_name does not exist.
Trace buffers can be plugged into the logging Python module with a TraceBufferHandler:
import logging
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
logHandler = exosapi.TraceBufferHandler("mybuf", 20480)
logHandler.setLevel(logging.DEBUG)
logger.addHandler(logHandler)
28
Parameters:
username (str): the name of the user to authenticate
password (str): the password to use
Returns:
ExosUser: successful authentication.
None: authentication failed
Parameters:
callback: function to call.
username (str): the username.
password (str): the password.
Returns:
ExosUser: successful authentication.
None: authentication failed
class exos.api.ExosUser(username)
An authenticated EXOS user. Instances of this class should be obtained via
authenticate_user(), authenticated_user_async() or
validate_session(). They should not be created directly.
start_session(remote_addr)
Start a session for this user. Remote_addr must be a string in IPv4 or IPv6 format. If this
user already has a session, AaaSessionExistsError is thrown.
end_session()
End a session for this user. If this user does not have a session,
AaaSessionExistsError is thrown.
Extreme Networks, Inc. All rights reserved.
29
read_write = None
True if the user has read/write privileges
session_cookie = None
The users session cookie, if a session has been started
session_start = None
The time, as returned by time.time(), the session was started
username = None
The users name
A session can be created, given an instance of ExosUser. Each session will be assigned a random
session cookie. Applications can return that cookie to their client and later use it to retrieve the
ExosUser again.
exos.api.validate_session(session_cookie)
Given a session_cookie, find the session.
Parameters:
session_cookie:
Returns:
ExosUser: if the session is found
None: if the session cannot be found
exos.api.get_sessions()
Return the list of active processes sessions. Will only return sessions created with
create_session().
Returns:
List of active sessions.
CLI Access
exos.api.exec_cli(cmds, timeout=0)
Send a list of commands to the CLI and return the output. This call will block until all
commands have completed. This is a non-interactive, session-less version of the CLI. It
doesnt prompt or page, meaning some CLI commands are not valid in it (example: dis
clipaging).
Parameters:
cmds (str): a list of commands.
timeout (int): timeout value, defaults to 0.
Returns:
A string of the CLI output (str).
Extreme Networks, Inc. All rights reserved.
30
Parameters:
cmds (str): a list of commands.
timeout (int): timeout value, defaults to 0.
callback will be called with each block of output.
exos.api.CLI_EVENT_EXEC_DONE
Used in an exec_cli_async() callback to indicate the current command has finished.
NOTE
The first release of the Python API included in EXOS 15.7.1 has a few limitations. One of
them is about the show config CLI command. The implementation of this command
is done in such a way that we cannot display the output yet.
At Layer 2, data plane packets can be identified by MAC address or Ethertype and
received into the Python application. From there, the application can process the packet
as it pleases. It can also generate new packets and inject them back into the data plane.
At Layer 3, the application, or individual sockets within the application, can bound to a
virtual router, thereby scoping it.
31
Layer 2
A Layer 2 socket is typically created as follows:
sock = socket.socket(socket.PF_EXOS_PACKET, socket.SOCK_RAW,
socket.htons(socket.ETH_P_ALL))
This socket will use the EXOS packet interface and will be able to send and receive raw packets to
and from the data plane.
Receive
Prior to receiving packets on an L2 socket, a filter must be set up:
sock.setup_filter("filter1",
action = socket.UP_AND_CONTINUE,
dstmac = [0x00,0xE0,0x2B,0x00,0x00,0x00])
This filter means that any packet coming into the data plane that is destined for MAC address
00:E0:2B:00:00:00 will be put on the socket and also forwarded (UP_AND_CONTINUE).
Ethertype could also be used:
sock.setup_filter("filter2", ethertype=0x0842)
Once the filter is set up, the socket behaves as any other socket. For example, select() can be
used to block for the next packet and recvfrom() can be used to retrieve a packet.
The following could be used to receive a packet:
pkt, addr = sock.recvfrom(8192)
In the above, pkt is a string containing the entire packet, including L2 headers, and addr is an
ExosSockAddr.
Send
When sending on an L2 socket using the EXOS packet interface, a few extra attributes are available:
slot_ports: A sequence of tuples. Each tuple is a (slot, port) pair identifying a port on
which the packet should be sent.
The following example sends on ports 1:1 and 1:2:
sock.sendto(pkt, slot_ports=[(1,1),(1,2)])
vlan_name: The VLAN on which the packet will be sent. The following example will forward
the packet on VLAN foo:
sock.sendto(pkt, vlan_name="foo")
32
Layer 3
Layer 3 sockets are created as normal. Examples include:
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Every EXOS process has an affinity to a particular virtual router. This means the process will tend to
do things, like open a socket, in the scope of that virtual router.
In the EXOS Python API, the VR affinity defaults to VR-Mgmt. However, this can be overridden at
process startup via the vr option. How this option is passed depends on the startup mechanism.
For example, when running the Python container (expy) directly, it is the -v option and when using
create process, it is the vr parameter.
An individual socket can be bound to a different virtual router:
tcp_sock.set_vr("VR-Default")
Impacket
Impacket is a Python library that helps encode and decode packets. It is included as a
convenience, but is not part of the EXOS Python API. More information can be found at the
Impacket website at:
http://corelabs.coresecurity.com/index.php?module=Wiki&action=view&type=tool&name=Impacket
The following example uses Impacket to build an ICMP packet. The packet is then sent:
ip = ImpactPacket.IP()
ip.set_ip_src("10.11.12.13")
ip.set_ip_dst("13.12.11.10")
icmp = ImpactPacket.ICMP()
icmp.set_icmp_type(icmp.ICMP_ECHO)
icmp.contains(ImpactPacket.Data("PYTHON_COMPLETES_EXOS"*5))
icmp.set_icmp_id(42)
icmp.set_icmp_cksum(0)
icmp.auto_checksum = 1
ip.contains(icmp)
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
sock.sendto(ip.get_packet(), ("13.12.11.10", 0))
Asynchronous Callback
Sockets can be used asynchronously by setting a callback. When data is received on the socket, the
callback is called. Typically, the callback would then receive on the socket and do something with
the packet:
def packet_handler(handled_sock):
pkt, addr = handled_sock.recvfrom(8192)
# Do something with pkt
sock.set_callback(packet_handler)
33
Registering a socket callback has a similar result as creating an extra thread to block on the socket,
except that the extra thread is not needed. Instead, a shared thread is used to block on many
sockets, including ones internal to EXOS.
API
class exos.api.expkt.socket(*args, **kwargs)
An EXPKT socket. This socket object subclasses Pythons socket class and can be used as
a drop-in replacement. It adds EXOS-specific features, such as virtual router awareness
and the ability to send and receive data plane traffic.
detach_all_filters()
Allows asynchronous reading of the socket. Takes a function to call back when a packet is
received on the socket. The function must accept one argument, the socket.
set_vr(vr_name)
Set the virtual router that the socket operates on. For L3 sockets only.
setup_filter(name, action=UP_AND_CONTINUE, dstmac=None,
ethertype=None, vr=None, slot-Port=False, ethertype_offset=24)
Set up the necessary filter to control what packets are sent to the socket.
name is required. Also, either dstmac, ethertype, or both are required.
Parameters:
name Name to use when creating an access-list for this filter. This name will be visible in
exos.api.expkt.PF_EXOS_PACKET
Socket domain for the EXOS packet (EXPKT) interface. Sockets created in this domain will
be able to send and receive raw packets to and from the data plane.
34
exos.api.expkt.ETH_P_ALL
exos.api.expkt.UP_AND_CONTINUE
Filter action indicating a matched packet must be put on the socket and also forwarded
by the data plane. The packet on the socket is thus a copy of the packet.
exos.api.expkt.UP_AND_DROP
Filter action indicating a matched packet must be put on the socket and not forwarded by
the data plane. This is not a copy of the packet, but the packet itself, ready for processing.
exos.api.expkt.EGRESS_AND_INGRESS
Filter direction indicating the filter matches both incoming and outgoing packets.
exos.api.expkt.SO_VRID
Socket option to get or set a sockets virtual router, to be used with getsockopt() and
setsockopt(). The value is the VRs id (vrid), not the VRs name. To set the virtual router by
name, see set_vrid().
For example, the following puts sock2 in the same VR as sock1:
vrid = sock1.getsockopt(SOL_SOCKET, socket.SO_VRID)
sock2.setsockopt(SOL_SOCKET, socket.SO_VRID, vrid)
Process Creation
Once we know how to use the EXOS Python API, we need to be able to create processes from our
Python programs. To create an EXOS process for a Python module, you need to use the following
command in EXOS CLI:
create process <name> python-module <python-module> {start [auto | ondemand]} {vr <vr-name>} {description <description>} {<arg1> {<arg2>
{<arg3> {<arg4> {<arg5> {<arg6> {<arg7> {<arg8> {<arg9>}}}}}}}}}
If on-demand is used, it is necessary to use the start process <name> command to start
it. The process is then a run to completion process.
35
If you want to delete a process that you have created, you need to use the following command in
EXOS: delete process <name>
This command doesnt prompt for a confirmation; only a dynamically created process can be
deleted. This EXOS command cannot delete a non user-created process. Heres an example:
(Engineering) VM1.2 # create process CheckVersionPy python-module
CheckVersionPython start auto
creating CheckVersionPy...
* (Engineering) PE2.3 #
* (Engineering) PE2.3 # show process
Process Name
Version Restart
State
Start Time
------------------------------------------------------------------------CheckVersionPy
User
0
Ready
Wed Jan 7 17:58:53 2015
aaa
3.0.0.3
0
Ready
Wed Jan 7 17:53:57 2015
acl
3.0.0.2
0
Ready
Wed Jan 7 17:53:57 2015
[]
NOTE
While it is perfectly fine to add the .py suffix of the filename in the scripting
environment, the .py suffix must be excluded from the create process CLI
command when creating a daemon.
36
-------------------------------------------------------------------------0
0
0
0
0
1
0
0
5
Memory Zone Green
Yellow
Orange
Red
-------------------------------------------------------------------------Green
0
0
0
0
-------------------------------------------------------------------------Commands:
Start
Stop
Resume
Shutdown
Kill
-------------------------------------------------------------------------0
0
0
0
0
-------------------------------------------------------------------------Resource Usage:
UserTime SysTime PageReclaim PageFault Up Since
Up Date
Up Time
-------------------------------------------------------------------------0.1
0.6
1256
15
Wed Jan 7 17:58:53 2015 00/00/00
00:00:44
-------------------------------------------------------------------------Thread Name
Pid
Tid
Delay Timeout Count
-------------------------------------------------------------------------main
1870
-1213282624
10
0
-------------------------------------------------------------------------* (Engineering) VM1.5 #
Python Native App, when started automatically, are persistent upon a reboot of the system. They
are part of the config file:
37
We can also easily list all the user-created processes running on a switch:
(Engineering) X440-8t.3 # show process | inc User
mySNTP
User
0
Ready
Tue Jan 20 21:12:47 2015
testapi
User
0
Ready
Tue Jan 20 20:58:05 2015
(Engineering) X440-8t.4 #
Examples
When dealing with the daemon environment, a few habits need to be changed compared to the
scripting environment.
The print function can only print to the console, as no other output is guaranteed in
the system when dealing with daemons. Instead of using the print function, it is
advised to use the trace logging capability.
The daemon should validate it is running in the Python container (expy), not as a script.
Basic Pattern
Below is an example showing the minimal pattern to follow:
import sys
import logging
from exos import api
# Setup logging.
logger = logging.getLogger('myprog')
logger.setLevel(logging.DEBUG)
logHandler = api.TraceBufferHandler("mybuf", 5120)
logHandler.setLevel(logging.DEBUG)
Extreme Networks, Inc. All rights reserved.
38
logHandler.setFormatter(logging.Formatter("%(threadName)s:%(name)s:%(funcN
ame)s.%(lineno)s:: %(message)s"))
logger.addHandler(logHandler)
try:
def main():
# Verify we're under EXPY. We can't live without it.
if not hasattr(sys, 'expy') or not sys.expy:
print "Must be run within expy"
return
# your program runs here
main()
except BaseException, e:
logger.error("Exception on startup, {}".format(e), exc_info=True)
Application Exercise
Lets write a first Python App that will simply give us the Python version running on the daemon
environment. This app will check that it is running in expy and, if so, will log the Python version in a
log file.
import sys
import logging
from exos import api
logger = logging.getLogger('MyFirstApp')
logger.setLevel(logging.DEBUG)
logHandler = api.TraceBufferHandler("MyBuffer", 5120)
logHandler.setLevel(logging.DEBUG)
logHandler.setFormatter(logging.Formatter("%(levelname)s:%(threadName)s:%(
name)s:%(funcName)s.%(lineno)s:: %(message)s"))
logger.addHandler(logHandler)
try:
def main():
if not hasattr(sys, 'expy') or not sys.expy:
print "Must be run within expy"
return
version =
str(sys.version_info[0])+'.'+str(sys.version_info[1])+'.'+str(sys.version_
info[2])
logger.info('Python version is: {}'.format(version))
main()
except BaseException, e:
logger.error("Exception on startup, {}".format(e), exc_info=True)
39
try:
def main():
if not hasattr(sys, 'expy') or not sys.expy:
print "Must be run within expy"
return
version =
str(sys.version_info[0])+'.'+str(sys.version_info[1])+'.'+str(sys.version_
info[2])
logger.info('Python version is: {}'.format(version))
main()
except BaseException, e:
logger.error("Exception on startup, {}".format(e), exc_info=True)
Then we define what logging level will be able to be registered in the logger. This is the usual Unix
logging levels:
Debug (10): typically any information useful for debugging.
Info (20): to give information on the application.
Warning (30): to give some information on a specific case.
Error (40): something went wrong.
Critical (50): this is very bad.
We can write these information with the appropriate level using:
logger.(info | warning | debug | error | critical)
40
After that, we create a log handler that will redirect the information that need to be written into a
given buffer. In this case, we are using the EXOS TraceBufferHandler() function, with a buffer
name and a buffer size. Once again we need to set the logging level to the handler, then we define
the formatting of the logs and we can finally associate the handler with the logger created.
The main() function is included into a Try/Except statement to catch any errors.
The first thing we check is that we are running in the daemon environment (expy). If this is not the
case, we exit the function, and so the program.
import sys
import logging
from exos import api
logger = logging.getLogger('MyFirstApp')
logger.setLevel(logging.DEBUG)
logHandler = api.TraceBufferHandler("MyBuffer", 5120)
logHandler.setLevel(logging.DEBUG)
logHandler.setFormatter(logging.Formatter("%(levelname)s:%(threadName)s:%(
name)s:%(funcName)s.%(lineno)s:: %(message)s"))
logger.addHandler(logHandler)
try:
def main():
if not hasattr(sys, 'expy') or not sys.expy:
print "Must be run within expy"
return
version =
str(sys.version_info[0])+'.'+str(sys.version_info[1])+'.'+str(sys.version_
info[2])
logger.info('Python version is: {}'.format(version))
main()
except BaseException, e:
logger.error("Exception on startup, {}".format(e), exc_info=True)
return
Finally, we run our code to check the Python version. We are logging the result into the logger, with
an info logging level.
import sys
import logging
from exos import api
logger = logging.getLogger('MyFirstApp')
logger.setLevel(logging.DEBUG)
logHandler = api.TraceBufferHandler("MyBuffer", 5120)
logHandler.setLevel(logging.DEBUG)
logHandler.setFormatter(logging.Formatter("%(levelname)s:%(threadName)s:%(
name)s:%(funcName)s.%(lineno)s:: %(message)s"))
41
logger.addHandler(logHandler)
try:
def main():
if not hasattr(sys, 'expy') or not sys.expy:
print "Must be run within expy"
return
version =
str(sys.version_info[0])+'.'+str(sys.version_info[1])+'.'+str(sys.version_
info[2])
logger.info('Python version is: {}'.format(version))
main()
except BaseException, e:
logger.error("Exception on startup, {}".format(e), exc_info=True)
return
Of course, this example is not really useful as an application because it runs only once, at startup.
Doing it that way, the process will last needlessly in EXOS. It would be more appropriate to execute
it on-demand.
mySNTP
Lets use a simple example to demonstrate the basic use of sockets. In the below Python code, we
are creating a SNTP Client program that will log the UTC time when executed. We have added the
use of an optional external file to display the correct time depending on the timezone. Once again,
this App is more like a script in its spirit, as it is executed only once, at startup. The goal here is more
to show how to use all the mechanisms of the API, rather than building a real and useful App.
#!/usr/bin/python
import sys
import struct
import time
from exos import api
from exos.api import expkt as socket
Extreme Networks, Inc. All rights reserved.
42
import logging
logger = logging.getLogger('mySNTP')
logger.setLevel(logging.DEBUG)
logHandler = api.TraceBufferHandler("mySNTPBuffer", 5120)
logHandler.setLevel(logging.DEBUG)
logHandler.setFormatter(logging.Formatter("%(levelname)s:%(name)s:%(funcNa
me)s.%(lineno)s:: %(message)s"))
logger.addHandler(logHandler)
NTP_SERVER = '194.57.169.1'
# ntp.univ-angers.fr
TIME1970 = 2208988800L
CONFIG_FILE = '/usr/local/cfg/mySNTP.txt'
try:
def main():
if not hasattr(sys, 'expy') or not sys.expy:
print "Must be run within expy"
return
try:
with open(CONFIG_FILE,'r') as fd:
s = fd.read()
except:
s = None
if s:
if s[3] == '+':
table = s.split('+')
GMT = int(table[1])
elif s[3] == '-':
table = s.split('-')
GMT = int(table[1])
GMT *= -1
else:
GMT = 0
else:
GMT = 0
43
main()
except BaseException, e:
logger.error("Exception on startup, {}".format(e), exc_info=True)
To better demonstrate the EXOS Pyhton API, we create the process in VR VR-Mgmt (the default VR
used if not specified). The switch running that Python app has internet connectivity only through
VR-Default, so we must change the VR in our code.
(Engineering) X460G2-24p-10G4.6 # create process mySNTP python-module
mySNTP start auto
creating mySNTP...
* (Engineering) X460G2-24p-10G4.7 #
Our program first tries to read the config file. If it gets an error trying to open it, we catch that error
through the try/except statement. We consider in that case that the file is not present, and we
continue the execution of the code.
try:
with open(CONFIG_FILE,'r') as fd:
s = fd.read()
except:
s = None
If we are able to open the file, we parse it to find what GMT offset is in it.
if s:
if s[3] == '+':
table = s.split('+')
GMT = int(table[1])
elif s[3] == '-':
table = s.split('-')
GMT = int(table[1])
GMT *= -1
else:
GMT = 0
else:
GMT = 0
If we cannot find the expected data, or if the file is not present, we simply set the GMT variable to 0.
The most interesting part is how we establish a network communication between our app and a
NTP server in the internet.
We first create a L3 socket, using IPv4 and UDP, named client. Then we make sure the socket is
bound to the required VR (VR-Default), so that communication is possible (our switch can
communicate with the outside only through this VR).
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.set_vr('VR-Default')
data = '\x1b' + 47 * '\0'
client.sendto(data, (NTP_SERVER, 123))
data, address = client.recvfrom(1024)
44
After creating the data we need to send (as defined by NTP), we send it to the configured server on
the appropriate UDP port (the NTP port).
Our program then waits for an answer (1024 bytes long) from the NTP server.
The rest of the code is just processing of this information to log it into our logger. We can see the
result looking at our logger:
(Engineering) X460G2-24p-10G4.7 # debug ems show trace mySNTP mySNTPBuffer
01/20/2015 22:11:18.003005 [185] <mySNTP:mySNTPBuffer> Begin trace buffer
01/20/2015 22:11:18.058736 [208] <mySNTP:mySNTPBuffer>
INFO:mySNTP:main.55:: Response received from ('194.57.169.1', 123)
01/20/2015 22:11:18.062694 [209] <mySNTP:mySNTPBuffer>
INFO:mySNTP:main.60:: UTC Time is Tue Jan 20 14:06:56 2015
01/20/2015 22:11:18.065896 [210] <mySNTP:mySNTPBuffer>
INFO:mySNTP:main.64:: Configured time in /usr/local/cfg/mySNTP.txt is
GMT+1: Time is Tue Jan 20 15:06:56 2015
(Engineering) X460G2-24p-10G4.8 #
45
Future
Over time, the differences between the two environmentsscripting and daemonshould be less
and less present.
Also, the version of the Python API included in 15.7.1 is the first release. There are a few limitations
(see the note for the show config example) as it addresses a few simple use cases. Extreme
Networks has already done quite a bit of work since this release and will cover more use cases in the
future.
More importantly, the Python SDK should evolve to allow more interaction with EXOS. Among the
key features that shall be included, we have:
CM backend
Extending CLI
Events
While Python enhancements are targeted for a future release, earlier tech releases might include
some of this functionality.
With CM backend support and CLI, the user/third-party has the opportunity to create new CLI
commands. For example, creating a process foo, it would be possible to do things like show foo
and so on.
The three of these features will allow the creation of a mini-controller, embedded in EXOS,
allowing the switch to do things when other things happen.
46
EXOS SDK
The EXOS Python API is another way to interact with EXOS. It interacts with the C API. The current
version of the C API allows for CLI creation and could be made available internally upon request.
This is extending the Open Architecture of EXOS, and provides an easy way around the controlled
release cycle of EXOS. It gives the opportunity to deliver prototype quickly and work on formalizing
it in a later release.
Northbound API
OF, XML, SNMP, CLI, Python
Open SDK
XML, C, Python
Event-Driven Agent
UPM
Automation
TCL, Python
Linux Core
Independent processes
47
Publication History
Date
Version
Author
Notes
January 2015
February 2015
0.1
1.0
Stphane Grosjean
Christina E. Mayr
Initial release
Edit and format
48