snimpy-0.8.1/ 0000755 0001750 0001750 00000000000 12232211722 013601 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/docs/ 0000755 0001750 0001750 00000000000 12232211722 014531 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/docs/license.rst 0000644 0001750 0001750 00000001745 12230342274 016721 0 ustar bernat bernat 0000000 0000000 ========
License
========
*Snimpy* is licensed under the ISC license. It basically means: do
whatever you want with it as long as the copyright sticks around, the
conditions are not modified and the disclaimer is present.
.. include:: ../AUTHORS.rst
ISC License
-----------
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
snimpy-0.8.1/docs/_static/ 0000755 0001750 0001750 00000000000 12232211722 016157 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/docs/_static/snimpy.svg 0000644 0001750 0001750 00000027443 12230062722 020233 0 ustar bernat bernat 0000000 0000000
image/svg+xml
snimpy-0.8.1/docs/contributing.rst 0000644 0001750 0001750 00000000040 12226612424 017773 0 ustar bernat bernat 0000000 0000000 .. include:: ../CONTRIBUTING.rst snimpy-0.8.1/docs/index.rst 0000644 0001750 0001750 00000004133 12230342274 016400 0 ustar bernat bernat 0000000 0000000 Snimpy: interactive SNMP tool
====================================================
*Snimpy* is a Python-based tool providing a simple interface to build
SNMP query. Here is a very simplistic example that allows us to
display the routing table of a given host::
load("IP-FORWARD-MIB")
m=M("localhost", "public", 2)
routes = m.ipCidrRouteNextHop
for x in routes:
net, netmask, tos, src = x
print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src))
You can either use *Snimpy* interactively throught its console
(derived from Python own console or from IPython_ if available) or
write *Snimpy* scripts which are just Python scripts with some global
variables available.
.. _IPython: http://ipython.org
Why another tool?
-----------------
There are a lot of SNMP tools available but most of them have
important drawback when you need to reliably automatize operations.
`snmpget`, `snmpset` and `snmpwalk` are difficult to use in
scripts. Errors are printed on standard output and there is no easy
way to tell if the command was successful or not. Moreover, results
can be multiline (a long HexString for example). At least,
automatisation is done through the shell and OID or bit manipulation
are quite difficult.
Net-SNMP provides officiel bindings for Perl and
Python. Unfortunately, the integration is quite poor. You don't have
an easy way to load and browse MIBs and error handling is
inexistant. For example, the Python bindings will return None for a
non-existant OID. Having to check for this on each request is quite
cumbersome.
For Python, there are other bindings. For example, pysnmp_ provides a
pure Python implementation. However, MIBs need to be
compiled. Moreover, the exposed interface is still low-level. Sending
a simple SNMP GET can either take 10 lines or one line wrapped into 10
lines.
.. _pysnmp: http://pysnmp.sourceforge.net/
The two main points of *Snimpy* are:
* very high-level interface
* raise exceptions when something goes wrong
Contents:
---------
.. toctree::
:maxdepth: 1
installation
usage
api
contributing
license
history
snimpy-0.8.1/docs/conf.py 0000644 0001750 0001750 00000002353 12230342274 016040 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os
rtd = os.environ.get('READTHEDOCS', None) == 'True'
cwd = os.getcwd()
project_root = os.path.dirname(cwd)
sys.path.insert(0, project_root)
# -- Don't try to load CFFI (doesn't work on RTD) ------------------------------
if rtd:
from mock import Mock
sys.modules['cffi'] = Mock()
import snimpy
# -- General configuration -----------------------------------------------------
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'Snimpy'
copyright = u'2013, Vincent Bernat'
version = snimpy.__version__
release = snimpy.__version__
exclude_patterns = ['_build']
pygments_style = 'sphinx'
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
sys.path.append(os.path.abspath('_themes'))
html_theme_path = ['_themes']
html_theme = 'flask'
html_static_path = ['_static']
html_use_modindex = False
html_theme_options = {
"index_logo": "snimpy.svg",
"index_logo_height": "200px"
}
htmlhelp_basename = 'snimpydoc'
snimpy-0.8.1/docs/history.rst 0000644 0001750 0001750 00000000033 12226612424 016767 0 ustar bernat bernat 0000000 0000000 .. include:: ../HISTORY.rst snimpy-0.8.1/docs/api.rst 0000644 0001750 0001750 00000001137 12230342274 016043 0 ustar bernat bernat 0000000 0000000 ==============
API reference
==============
While *Snimpy* is targeted at being used interactively or through
simple scripts, you can also use it from your Python program. It
provides a high-level interface as well as lower-level ones.
:mod:`manager` module
----------------------
.. automodule:: snimpy.manager
:members: Manager, load
:mod:`mib` module
------------------
.. automodule:: snimpy.mib
:members:
:mod:`snmp` module
-------------------
.. automodule:: snimpy.snmp
:members:
:mod:`basictypes` module
-------------------------
.. automodule:: snimpy.basictypes
:members:
snimpy-0.8.1/docs/_themes/ 0000755 0001750 0001750 00000000000 12232211722 016155 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/docs/_themes/flask_theme_support.py 0000644 0001750 0001750 00000011413 12226612364 022617 0 ustar bernat bernat 0000000 0000000 # flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
class FlaskyStyle(Style):
background_color = "#f8f8f8"
default_style = ""
styles = {
# No corresponding class for the following:
#Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p'
# because special names such as Name.Class, Name.Function, etc.
# are not recognized as such later in the parsing, we choose them
# to look the same as ordinary variables.
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
}
snimpy-0.8.1/docs/_themes/flask/ 0000755 0001750 0001750 00000000000 12232211722 017255 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/docs/_themes/flask/layout.html 0000644 0001750 0001750 00000001265 12226612364 021476 0 ustar bernat bernat 0000000 0000000 {%- extends "basic/layout.html" %}
{%- block extrahead %}
{{ super() }}
{% if theme_touch_icon %}
{% endif %}
{% endblock %}
{%- block relbar2 %}{% endblock %}
{% block header %}
{{ super() }}
{% if pagename == 'index' %}
{% endif %}
{% endblock %}
{%- block footer %}
{% if pagename == 'index' %}
{% endif %}
{%- endblock %}
snimpy-0.8.1/docs/_themes/flask/static/ 0000755 0001750 0001750 00000000000 12232211722 020544 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/docs/_themes/flask/static/flasky.css_t 0000644 0001750 0001750 00000021546 12226612364 023114 0 ustar bernat bernat 0000000 0000000 /*
* flasky.css_t
* ~~~~~~~~~~~~
*
* :copyright: Copyright 2010 by Armin Ronacher.
* :license: Flask Design License, see LICENSE for details.
*/
{% set page_width = '940px' %}
{% set sidebar_width = '220px' %}
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'Georgia', serif;
font-size: 17px;
background-color: white;
color: #000;
margin: 0;
padding: 0;
}
div.document {
width: {{ page_width }};
margin: 30px auto 0 auto;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 {{ sidebar_width }};
}
div.sphinxsidebar {
width: {{ sidebar_width }};
}
hr {
border: 1px solid #B1B4B6;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 0 30px;
}
img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
div.footer {
width: {{ page_width }};
margin: 20px auto 30px auto;
font-size: 14px;
color: #888;
text-align: right;
}
div.footer a {
color: #888;
}
div.related {
display: none;
}
div.sphinxsidebar a {
color: #444;
text-decoration: none;
border-bottom: 1px dotted #999;
}
div.sphinxsidebar a:hover {
border-bottom: 1px solid #999;
}
div.sphinxsidebar {
font-size: 14px;
line-height: 1.5;
}
div.sphinxsidebarwrapper {
padding: 18px 10px;
}
div.sphinxsidebarwrapper p.logo {
padding: 0 0 20px 0;
margin: 0;
text-align: center;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: 'Garamond', 'Georgia', serif;
color: #444;
font-size: 24px;
font-weight: normal;
margin: 0 0 5px 0;
padding: 0;
}
div.sphinxsidebar h4 {
font-size: 20px;
}
div.sphinxsidebar h3 a {
color: #444;
}
div.sphinxsidebar p.logo a,
div.sphinxsidebar h3 a,
div.sphinxsidebar p.logo a:hover,
div.sphinxsidebar h3 a:hover {
border: none;
}
div.sphinxsidebar p {
color: #555;
margin: 10px 0;
}
div.sphinxsidebar ul {
margin: 10px 0;
padding: 0;
color: #000;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: 'Georgia', serif;
font-size: 1em;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #004B6B;
text-decoration: underline;
}
a:hover {
color: #6D4100;
text-decoration: underline;
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
margin: 30px 0px 10px 0px;
padding: 0;
}
{% if theme_index_logo %}
div.indexwrapper h1 {
text-indent: -999999px;
background: url({{ theme_index_logo }}) no-repeat center center;
height: {{ theme_index_logo_height }};
}
{% endif %}
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: #ddd;
padding: 0 4px;
text-decoration: none;
}
a.headerlink:hover {
color: #444;
background: #eaeaea;
}
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
div.admonition {
background: #fafafa;
margin: 20px -30px;
padding: 10px 30px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
div.admonition tt.xref, div.admonition a tt {
border-bottom: 1px solid #fafafa;
}
dd div.admonition {
margin-left: -60px;
padding-left: 60px;
}
div.admonition p.admonition-title {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
font-size: 24px;
margin: 0 0 10px 0;
padding: 0;
line-height: 1;
}
div.admonition p.last {
margin-bottom: 0;
}
div.highlight {
background-color: white;
}
dt:target, .highlight {
background: #FAF3E8;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre, tt {
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
font-size: 0.9em;
}
img.screenshot {
}
tt.descname, tt.descclassname {
font-size: 0.95em;
}
tt.descname {
padding-right: 0.08em;
}
img.screenshot {
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils {
border: 1px solid #888;
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils td, table.docutils th {
border: 1px solid #888;
padding: 0.25em 0.7em;
}
table.field-list, table.footnote {
border: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
table.footnote {
margin: 15px 0;
width: 100%;
border: 1px solid #eee;
background: #fdfdfd;
font-size: 0.9em;
}
table.footnote + table.footnote {
margin-top: -15px;
border-top: none;
}
table.field-list th {
padding: 0 0.8em 0 0;
}
table.field-list td {
padding: 0;
}
table.footnote td.label {
width: 0px;
padding: 0.3em 0 0.3em 0.5em;
}
table.footnote td {
padding: 0.3em 0.5em;
}
dl {
margin: 0;
padding: 0;
}
dl dd {
margin-left: 30px;
}
blockquote {
margin: 0 0 0 30px;
padding: 0;
}
ul, ol {
margin: 10px 0 10px 30px;
padding: 0;
}
pre {
background: #eee;
padding: 7px 30px;
margin: 15px -30px;
line-height: 1.3em;
}
dl pre, blockquote pre, li pre {
margin-left: -60px;
padding-left: 60px;
}
dl dl pre {
margin-left: -90px;
padding-left: 90px;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
}
tt.xref, a tt {
background-color: #FBFBFB;
border-bottom: 1px solid white;
}
a.reference {
text-decoration: none;
border-bottom: 1px dotted #004B6B;
}
a.reference:hover {
border-bottom: 1px solid #6D4100;
}
a.footnote-reference {
text-decoration: none;
font-size: 0.7em;
vertical-align: top;
border-bottom: 1px dotted #004B6B;
}
a.footnote-reference:hover {
border-bottom: 1px solid #6D4100;
}
a:hover tt {
background: #EEE;
}
@media screen and (max-width: 870px) {
div.sphinxsidebar {
display: none;
}
div.document {
width: 100%;
}
div.documentwrapper {
margin-left: 0;
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
}
div.bodywrapper {
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
margin-left: 0;
}
ul {
margin-left: 0;
}
.document {
width: auto;
}
.footer {
width: auto;
}
.bodywrapper {
margin: 0;
}
.footer {
width: auto;
}
.github {
display: none;
}
}
@media screen and (max-width: 875px) {
body {
margin: 0;
padding: 20px 30px;
}
div.documentwrapper {
float: none;
background: white;
}
div.sphinxsidebar {
display: block;
float: none;
width: 102.5%;
margin: 50px -30px -20px -30px;
padding: 10px 20px;
background: #333;
color: white;
}
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
div.sphinxsidebar h3 a {
color: white;
}
div.sphinxsidebar a {
color: #aaa;
}
div.sphinxsidebar p.logo {
display: none;
}
div.document {
width: 100%;
margin: 0;
}
div.related {
display: block;
margin: 0;
padding: 10px 0 20px 0;
}
div.related ul,
div.related ul li {
margin: 0;
padding: 0;
}
div.footer {
display: none;
}
div.bodywrapper {
margin: 0;
}
div.body {
min-height: 0;
padding: 0;
}
.rtd_doc_footer {
display: none;
}
.document {
width: auto;
}
.footer {
width: auto;
}
.footer {
width: auto;
}
.github {
display: none;
}
}
/* scrollbars */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-button:start:decrement,
::-webkit-scrollbar-button:end:increment {
display: block;
height: 10px;
}
::-webkit-scrollbar-button:vertical:increment {
background-color: #fff;
}
::-webkit-scrollbar-track-piece {
background-color: #eee;
-webkit-border-radius: 3px;
}
::-webkit-scrollbar-thumb:vertical {
height: 50px;
background-color: #ccc;
-webkit-border-radius: 3px;
}
::-webkit-scrollbar-thumb:horizontal {
width: 50px;
background-color: #ccc;
-webkit-border-radius: 3px;
}
/* misc. */
.revsys-inline {
display: none!important;
} snimpy-0.8.1/docs/_themes/flask/relations.html 0000644 0001750 0001750 00000001116 12226612364 022154 0 ustar bernat bernat 0000000 0000000 Related Topics
snimpy-0.8.1/docs/_themes/flask/theme.conf 0000644 0001750 0001750 00000000244 12226612364 021240 0 ustar bernat bernat 0000000 0000000 [theme]
inherit = basic
stylesheet = flasky.css
pygments_style = flask_theme_support.FlaskyStyle
[options]
index_logo = ''
index_logo_height = 120px
touch_icon =
snimpy-0.8.1/docs/_themes/flask_small/ 0000755 0001750 0001750 00000000000 12232211722 020445 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/docs/_themes/flask_small/layout.html 0000644 0001750 0001750 00000001253 12226612364 022663 0 ustar bernat bernat 0000000 0000000 {% extends "basic/layout.html" %}
{% block header %}
{{ super() }}
{% if pagename == 'index' %}
{% endif %}
{% endblock %}
{% block footer %}
{% if pagename == 'index' %}
{% endif %}
{% endblock %}
{# do not display relbars #}
{% block relbar1 %}{% endblock %}
{% block relbar2 %}
{% if theme_github_fork %}
{% endif %}
{% endblock %}
{% block sidebar1 %}{% endblock %}
{% block sidebar2 %}{% endblock %}
snimpy-0.8.1/docs/_themes/flask_small/static/ 0000755 0001750 0001750 00000000000 12232211722 021734 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/docs/_themes/flask_small/static/flasky.css_t 0000644 0001750 0001750 00000011001 12226612364 024265 0 ustar bernat bernat 0000000 0000000 /*
* flasky.css_t
* ~~~~~~~~~~~~
*
* Sphinx stylesheet -- flasky theme based on nature theme.
*
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'Georgia', serif;
font-size: 17px;
color: #000;
background: white;
margin: 0;
padding: 0;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 40px auto 0 auto;
width: 700px;
}
hr {
border: 1px solid #B1B4B6;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 30px 30px;
}
img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
div.footer {
text-align: right;
color: #888;
padding: 10px;
font-size: 14px;
width: 650px;
margin: 0 auto 40px auto;
}
div.footer a {
color: #888;
text-decoration: underline;
}
div.related {
line-height: 32px;
color: #888;
}
div.related ul {
padding: 0 0 0 10px;
}
div.related a {
color: #444;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #004B6B;
text-decoration: underline;
}
a:hover {
color: #6D4100;
text-decoration: underline;
}
div.body {
padding-bottom: 40px; /* saved for footer */
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
margin: 30px 0px 10px 0px;
padding: 0;
}
{% if theme_index_logo %}
div.indexwrapper h1 {
text-indent: -999999px;
background: url({{ theme_index_logo }}) no-repeat center center;
height: {{ theme_index_logo_height }};
}
{% endif %}
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: white;
padding: 0 4px;
text-decoration: none;
}
a.headerlink:hover {
color: #444;
background: #eaeaea;
}
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
div.admonition {
background: #fafafa;
margin: 20px -30px;
padding: 10px 30px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
div.admonition p.admonition-title {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
font-size: 24px;
margin: 0 0 10px 0;
padding: 0;
line-height: 1;
}
div.admonition p.last {
margin-bottom: 0;
}
div.highlight{
background-color: white;
}
dt:target, .highlight {
background: #FAF3E8;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre, tt {
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
font-size: 0.85em;
}
img.screenshot {
}
tt.descname, tt.descclassname {
font-size: 0.95em;
}
tt.descname {
padding-right: 0.08em;
}
img.screenshot {
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils {
border: 1px solid #888;
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils td, table.docutils th {
border: 1px solid #888;
padding: 0.25em 0.7em;
}
table.field-list, table.footnote {
border: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
table.footnote {
margin: 15px 0;
width: 100%;
border: 1px solid #eee;
}
table.field-list th {
padding: 0 0.8em 0 0;
}
table.field-list td {
padding: 0;
}
table.footnote td {
padding: 0.5em;
}
dl {
margin: 0;
padding: 0;
}
dl dd {
margin-left: 30px;
}
pre {
padding: 0;
margin: 15px -30px;
padding: 8px;
line-height: 1.3em;
padding: 7px 30px;
background: #eee;
border-radius: 2px;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
}
dl pre {
margin-left: -60px;
padding-left: 60px;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
}
tt.xref, a tt {
background-color: #FBFBFB;
}
a:hover tt {
background: #EEE;
}
snimpy-0.8.1/docs/_themes/flask_small/theme.conf 0000644 0001750 0001750 00000000270 12226612364 022427 0 ustar bernat bernat 0000000 0000000 [theme]
inherit = basic
stylesheet = flasky.css
nosidebar = true
pygments_style = flask_theme_support.FlaskyStyle
[options]
index_logo = ''
index_logo_height = 120px
github_fork = ''
snimpy-0.8.1/docs/_themes/README 0000644 0001750 0001750 00000002105 12226612364 017045 0 ustar bernat bernat 0000000 0000000 Flask Sphinx Styles
===================
This repository contains sphinx styles for Flask and Flask related
projects. To use this style in your Sphinx documentation, follow
this guide:
1. put this folder as _themes into your docs folder. Alternatively
you can also use git submodules to check out the contents there.
2. add this to your conf.py:
sys.path.append(os.path.abspath('_themes'))
html_theme_path = ['_themes']
html_theme = 'flask'
The following themes exist:
- 'flask' - the standard flask documentation theme for large
projects
- 'flask_small' - small one-page theme. Intended to be used by
very small addon libraries for flask.
The following options exist for the flask_small theme:
[options]
index_logo = '' filename of a picture in _static
to be used as replacement for the
h1 in the index.rst file.
index_logo_height = 120px height of the index logo
github_fork = '' repository name on github for the
"fork me" badge
snimpy-0.8.1/docs/_themes/.gitignore 0000644 0001750 0001750 00000000026 12226612364 020155 0 ustar bernat bernat 0000000 0000000 *.pyc
*.pyo
.DS_Store
snimpy-0.8.1/docs/_themes/LICENSE 0000644 0001750 0001750 00000003375 12226612364 017204 0 ustar bernat bernat 0000000 0000000 Copyright (c) 2010 by Armin Ronacher.
Some rights reserved.
Redistribution and use in source and binary forms of the theme, with or
without modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
We kindly ask you to only use these themes in an unmodified manner just
for Flask and Flask-related products, not for unrelated projects. If you
like the visual style and want to use it for your own projects, please
consider making some larger changes to the themes (such as changing
font faces, sizes, colors or margins).
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
snimpy-0.8.1/docs/installation.rst 0000644 0001750 0001750 00000000465 12230342274 017776 0 ustar bernat bernat 0000000 0000000 ============
Installation
============
At the command line::
$ easy_install snimpy
Or, if you have virtualenvwrapper installed::
$ mkvirtualenv snimpy
$ pip install snimpy
On Debian and Ubuntu, *Snimpy* is also available as a package you can
install with::
$ sudo apt-get install snimpy
snimpy-0.8.1/docs/usage.rst 0000644 0001750 0001750 00000013031 12230342274 016372 0 ustar bernat bernat 0000000 0000000 ========
Usage
========
Invocation
----------
There are three ways to use *Snimpy*:
1. Interactively through a console.
2. As a script interpreter.
3. As a regular Python module.
Interactive use
+++++++++++++++
*Snimpy* can be invoked with either `snimpy` or `python -m
snimpy`. Without any other arhument, the interactive console is
spawned. Otherwise, the given script is executed and the remaining
arguments are served as arguments for the script.
When running interactively, you get a classic Python
environment. There are two additional objects available:
* The `load()` method that takes a MIB name or a path to a
filename. The MIB will be loaded into memory and made available in
all SNMP managers::
load("IF-MIB")
load("/usr/share/mibs/ietf/IF-MIB")
* The `M` class which is used to instantiate a manager (a SNMP
client)::
m = M()
m = M(host="localhost", community="private", version=2)
m = M("localhost", "private", 2)
m = M(community="private")
m = M(version=3,
secname="readonly",
authprotocol="MD5", authpassword="authpass",
privprotocol="AES", privpassword="privpass")
A manager instance contains all the scalars and the columns in MIB
loaded with the `load()` method. There is no table, node or other
entities. For a scalar, getting and setting a value is a simple as::
print m.sysDescr
m.sysName = "newhostname"
For a column, you get a dictionary-like interface::
for index in m.ifDescr: print repr(m.ifDescr[i])
m.ifAdminStatus[3] = "down"
If you want to group several write into a single request, you can do
it with `with` keyword::
with M("localhost", "private") as m:
m.sysName = "toto"
m.ifAdminStatus[20] = "down"
There is also a caching mechanism which is disabled by default::
import time
m = M("localhost", cache=True)
print m.sysUpTime
time.sleep(1)
print m.sysUpTime
time.sleep(1)
print m.sysUpTime
time.sleep(10)
print m.sysUpTime
You can also specify the number of seconds data should be cached::
m = M("localhost", cache=20)
It's also possible to set a custom timeout and a custom value for the
number of retries. For example, to wait 2.5 seconds before timeout
occurs and retry 10 times, you can use::
m = M("localhost", timeout=2.5, retry=10)
*Snimpy* will stop on any error with an exception. This allows you to
not check the result at each step. Your script can't go awry. If this
behaviour does not suit you, it is possible to suppress exceptions
when querying inexistant objects. Instead of an exception, you'll get
`None`::
m = M("localhost", none=True)
Script interpreter
++++++++++++++++++
*Snimpy* can be run as a script interpreter. There are two ways to do
this. The first one is to invoke *Snimpy* and provide a script name as
well as any argument you want to pass to the script::
$ snimpy example-script.py arg1 arg2
$ python -m snimpy example-script.py arg1 arg2
The second one is to use *Snimpy* as a shebang_ interpreter. For
example, here is a simple script::
#!/usr/bin/env snimpy
load("IF-MIB")
m = M("localhost")
print(m.ifDescr[0])
The script can be invoked as any shell script.
.. _shebang: http://en.wikipedia.org/wiki/Shebang_(Unix)
Inside the script, you can use any valid Python code. You also get the
`load()` method and the `M` class available, like for the interactive
use.
Regular Python module
+++++++++++++++++++++
*Snimpy* can also be imported as a regular Python module::
from snimpy.manager import Manager as M
from snimpy.manager import load
load("IF-MIB")
m = M("localhost")
print(m.ifDescr[0])
About "major SMI errors"
------------------------
If you get an exception like `RAPID-CITY contains major SMI errors
(check with smilint -s -l1)`, this means that there are some grave
errors in this MIB which may lead to segfaults if the MIB is used as
is. Usually, this means that some identifier are unknown. Use `smilint
-s -l1 YOUR-MIB` to see what the problem is and try to solve all
problems reported by lines beginning by `[1]`.
For example::
$ smilint -s -l1 rapid_city.mib
rapid_city.mib:30: [1] failed to locate MIB module `IGMP-MIB'
rapid_city.mib:32: [1] failed to locate MIB module `DVMRP-MIB'
rapid_city.mib:34: [1] failed to locate MIB module `IGMP-MIB'
rapid_city.mib:27842: [1] unknown object identifier label `igmpInterfaceIfIndex'
rapid_city.mib:27843: [1] unknown object identifier label `igmpInterfaceQuerier'
rapid_city.mib:27876: [1] unknown object identifier label `dvmrpInterfaceIfIndex'
rapid_city.mib:27877: [1] unknown object identifier label `dvmrpInterfaceOperState'
rapid_city.mib:27894: [1] unknown object identifier label `dvmrpNeighborIfIndex'
rapid_city.mib:27895: [1] unknown object identifier label `dvmrpNeighborAddress'
rapid_city.mib:32858: [1] unknown object identifier label `igmpCacheAddress'
rapid_city.mib:32858: [1] unknown object identifier label `igmpCacheIfIndex'
To solve the problem here, load `IGMP-MIB` and `DVMRP-MIB` before
loading `rapid_city.mib`. `IGMP-MIB` should be pretty easy to
find. For `DVMRP-MIB`, try Google.
Download it and use `smistrip` to get the MIB. You can check that the
problem is solved with this command::
$ smilint -p ../cisco/IGMP-MIB.my -p ./DVMRP-MIB -s -l1 rapid_city.mib
You will get a lot of errors in `IGMP-MIB` and `DVMRP-MIB` but no line
with `[1]`: everything should be fine. To load `rapid_city.mib`, you
need to do this::
load("../cisco/IGMP-MIB.my")
load("./DVMRP-MIB")
load("rapid_city.mib")
snimpy-0.8.1/snimpy/ 0000755 0001750 0001750 00000000000 12232211722 015120 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/snimpy/mib.py 0000644 0001750 0001750 00000047177 12232161766 016275 0 ustar bernat bernat 0000000 0000000 #
# snimpy -- Interactive SNMP tool
#
# Copyright (C) Vincent Bernat
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""This module is a low-level interface to manipulate and
extract information from MIB files. It is a CFFI_ wrapper around
libsmi_. You may find convenient to use it in other projects but the
wrapper is merely here to serve *Snimpy* use and is therefore
incomplete.
.. _libsmi: http://www.ibr.cs.tu-bs.de/projects/libsmi/
.. _CFFI: http://cffi.readthedocs.org/
"""
from snimpy import cffi_fix # nopep8
from cffi import FFI
ffi = FFI()
ffi.cdef("""
typedef char *SmiIdentifier;
typedef unsigned long SmiUnsigned32;
typedef long SmiInteger32;
typedef unsigned long long SmiUnsigned64;
typedef long long SmiInteger64;
typedef unsigned int SmiSubid;
typedef float SmiFloat32;
typedef double SmiFloat64;
typedef long double SmiFloat128;
typedef enum SmiBasetype {
SMI_BASETYPE_INTEGER32,
SMI_BASETYPE_OCTETSTRING,
SMI_BASETYPE_OBJECTIDENTIFIER,
SMI_BASETYPE_UNSIGNED32,
SMI_BASETYPE_INTEGER64,
SMI_BASETYPE_UNSIGNED64,
SMI_BASETYPE_ENUM,
SMI_BASETYPE_BITS,
...
} SmiBasetype;
typedef struct SmiType {
SmiIdentifier name;
SmiBasetype basetype;
char *format;
...;
} SmiType;
typedef enum SmiIndexkind {
SMI_INDEX_INDEX,
SMI_INDEX_AUGMENT,
...
} SmiIndexkind;
typedef unsigned int SmiNodekind;
#define SMI_NODEKIND_NODE ...
#define SMI_NODEKIND_SCALAR ...
#define SMI_NODEKIND_TABLE ...
#define SMI_NODEKIND_ROW ...
#define SMI_NODEKIND_COLUMN ...
typedef struct SmiNode {
SmiIdentifier name;
unsigned int oidlen;
SmiSubid *oid;
char *format;
SmiIndexkind indexkind;
int implied;
SmiNodekind nodekind;
...;
} SmiNode;
typedef struct SmiValue {
SmiBasetype basetype;
union {
SmiUnsigned64 unsigned64;
SmiInteger64 integer64;
SmiUnsigned32 unsigned32;
SmiInteger32 integer32;
SmiFloat32 float32;
SmiFloat64 float64;
SmiFloat128 float128;
SmiSubid *oid;
char *ptr;
} value;
...;
} SmiValue;
typedef struct SmiRange {
SmiValue minValue;
SmiValue maxValue;
} SmiRange;
typedef struct SmiModule {
SmiIdentifier name;
int conformance;
...;
} SmiModule;
typedef struct SmiElement {
...;
} SmiElement;
typedef struct SmiNamedNumber {
SmiIdentifier name;
SmiValue value;
} SmiNamedNumber;
int smiInit(const char *);
void smiExit(void);
void smiSetErrorLevel(int);
void smiSetFlags(int);
char *smiLoadModule(const char *);
SmiModule *smiGetModule(const char *);
SmiModule *smiGetNodeModule(SmiNode *);
SmiType *smiGetNodeType(SmiNode *);
SmiType *smiGetParentType(SmiType *);
char *smiRenderNode(SmiNode *, int);
SmiElement *smiGetFirstElement(SmiNode *);
SmiElement *smiGetNextElement(SmiElement *);
SmiNode *smiGetElementNode(SmiElement *);
SmiRange *smiGetFirstRange(SmiType *);
SmiRange *smiGetNextRange(SmiRange *);
SmiNode *smiGetNode(SmiModule *, const char *);
SmiNode *smiGetFirstNode(SmiModule *, SmiNodekind);
SmiNode *smiGetNextNode(SmiNode *, SmiNodekind);
SmiNode *smiGetParentNode(SmiNode *);
SmiNode *smiGetRelatedNode(SmiNode *);
SmiNode *smiGetFirstChildNode(SmiNode *);
SmiNode *smiGetNextChildNode(SmiNode *);
SmiNamedNumber *smiGetFirstNamedNumber(SmiType *);
SmiNamedNumber *smiGetNextNamedNumber(SmiNamedNumber *);
#define SMI_FLAG_ERRORS ...
#define SMI_FLAG_RECURSIVE ...
#define SMI_RENDER_ALL ...
""")
_smi = ffi.verify("""
#include
""", libraries=["smi"])
class SMIException(Exception):
"""SMI related exception. Any exception thrown in this module is
inherited from this one."""
class Node(object):
"""MIB node. An instance of this class represents a MIB node. It
can be specialized by other classes, like :class:`Scalar`,
:class:`Table`, :class:`Column`, :class:`Node`.
"""
def __init__(self, node):
"""Create a new MIB node.
:param node: libsmi node supporting this node.
"""
self.node = node
@property
def type(self):
"""Get the basic type associated with this node.
:return: The class from :mod:`basictypes` module which can
represent the node. When retrieving a valid value for
this node, the returned class can be instanciated to get
an appropriate representation.
"""
from snimpy import basictypes
t = _smi.smiGetNodeType(self.node)
target = {
_smi.SMI_BASETYPE_INTEGER32: basictypes.Integer,
_smi.SMI_BASETYPE_INTEGER64: basictypes.Integer,
_smi.SMI_BASETYPE_UNSIGNED32: {b"TimeTicks": basictypes.Timeticks,
None: basictypes.Unsigned32},
_smi.SMI_BASETYPE_UNSIGNED64: basictypes.Unsigned64,
_smi.SMI_BASETYPE_OCTETSTRING: {b"IpAddress": basictypes.IpAddress,
None: basictypes.OctetString},
_smi.SMI_BASETYPE_OBJECTIDENTIFIER: basictypes.Oid,
_smi.SMI_BASETYPE_ENUM: {b"TruthValue": basictypes.Boolean,
None: basictypes.Enum},
_smi.SMI_BASETYPE_BITS: basictypes.Bits
}.get(t.basetype, None)
if isinstance(target, dict):
tt = _smi.smiGetParentType(t)
target = target.get((t.name != ffi.NULL and ffi.string(t.name)) or
(tt.name != ffi.NULL and ffi.string(
tt.name)) or None,
target.get(None, None))
if target is None:
raise SMIException("unable to retrieve type of node")
return target
@property
def fmt(self):
"""Get node format. The node format is a string to use to display
a user-friendly version of the node. This is can be used for
both octet strings or integers (to make them appear as decimal
numbers).
:return: The node format as a string or None if there is no
format available.
"""
t = _smi.smiGetNodeType(self.node)
tt = _smi.smiGetParentType(t)
f = (t != ffi.NULL and t.format != ffi.NULL and ffi.string(t.format) or
tt != ffi.NULL and tt.format != ffi.NULL and
ffi.string(tt.format)) or None
if f is None:
return None
return f.decode("ascii")
@property
def oid(self):
"""Get OID for the current node. The OID can then be used to request
the node from an SNMP agent.
:return: OID as a tuple
"""
return tuple([self.node.oid[i] for i in range(self.node.oidlen)])
@property
def ranges(self):
"""Get node ranges. An node can be restricted by a set of
ranges. For example, an integer can only be provided between
two values. For strings, the restriction is on the length of
the string.
The returned value can be `None` if no restriction on range
exists for the current node, a single value if the range is
fixed or a list of tuples or fixed values otherwise.
:return: The valid range for this node.
"""
t = _smi.smiGetNodeType(self.node)
if t == ffi.NULL:
return None
ranges = []
range = _smi.smiGetFirstRange(t)
while range != ffi.NULL:
m1 = self._convert(range.minValue)
m2 = self._convert(range.maxValue)
if m1 == m2:
ranges.append(m1)
else:
ranges.append((m1, m2))
range = _smi.smiGetNextRange(range)
if len(ranges) == 0:
return None
if len(ranges) == 1:
return ranges[0]
return ranges
@property
def enum(self):
"""Get possible enum values. When the node can only take a discrete
number of values, those values are defined in the MIB and can
be retrieved through this property.
:return: The dictionary of possible values keyed by the integer value.
"""
t = _smi.smiGetNodeType(self.node)
if t == ffi.NULL or t.basetype not in (_smi.SMI_BASETYPE_ENUM,
_smi.SMI_BASETYPE_BITS):
return None
result = {}
element = _smi.smiGetFirstNamedNumber(t)
while element != ffi.NULL:
result[self._convert(element.value)] = ffi.string(
element.name).decode("ascii")
element = _smi.smiGetNextNamedNumber(element)
return result
def __str__(self):
return ffi.string(self.node.name).decode("ascii")
def __repr__(self):
r = _smi.smiRenderNode(self.node, _smi.SMI_RENDER_ALL)
if r == ffi.NULL:
return "".format(
self.__class__.__name__, hex(id(self)))
module = _smi.smiGetNodeModule(self.node)
if module == ffi.NULL:
raise SMIException("unable to get module for {0}".format(
self.node.name))
return "<{0} {1} from '{2}'>".format(self.__class__.__name__,
ffi.string(r),
ffi.string(module.name))
def _convert(self, value):
attr = {_smi.SMI_BASETYPE_INTEGER32: "integer32",
_smi.SMI_BASETYPE_UNSIGNED32: "unsigned32",
_smi.SMI_BASETYPE_INTEGER64: "integer64",
_smi.SMI_BASETYPE_UNSIGNED64: "unsigned64"}.get(value.basetype,
None)
if attr is None:
raise SMIException("unexpected type found in range")
return getattr(value.value, attr)
class Scalar(Node):
"""MIB scalar node. This class represents a scalar value in the
MIB. A scalar value is a value not contained in a table.
"""
class Table(Node):
"""MIB table node. This class represents a table. A table is an
ordered collection of objects consisting of zero or more
rows. Each object in the table is identified using an index. An
index can be a single value or a list of values.
"""
@property
def columns(self):
"""Get table columns. The columns are the different kind of objects
that can be retrieved in a table.
:return: list of table columns (:class:`Column` instances)
"""
child = _smi.smiGetFirstChildNode(self.node)
if child == ffi.NULL:
return []
if child.nodekind != _smi.SMI_NODEKIND_ROW:
raise SMIException("child {0} of {1} is not a row".format(
ffi.string(child.name),
ffi.string(self.node.name)))
columns = []
child = _smi.smiGetFirstChildNode(child)
while child != ffi.NULL:
if child.nodekind != _smi.SMI_NODEKIND_COLUMN:
raise SMIException("child {0} of {1} is not a column".format(
ffi.string(child.name),
ffi.string(self.node.name)))
columns.append(Column(child))
child = _smi.smiGetNextChildNode(child)
return columns
@property
def _row(self):
"""Get the table row.
:return: row object (as an opaque object)
"""
child = _smi.smiGetFirstChildNode(self.node)
if child != ffi.NULL and child.indexkind == _smi.SMI_INDEX_AUGMENT:
child = _smi.smiGetRelatedNode(child)
if child == ffi.NULL:
raise SMIException("AUGMENT index for {0} but "
"unable to retrieve it".format(
ffi.string(self.node.name)))
if child == ffi.NULL:
raise SMIException("{0} does not have a row".format(
ffi.string(self.node.name)))
if child.nodekind != _smi.SMI_NODEKIND_ROW:
raise SMIException("child {0} of {1} is not a row".format(
ffi.string(child.name),
ffi.string(self.node.name)))
if child.indexkind != _smi.SMI_INDEX_INDEX:
raise SMIException("child {0} of {1} has an unhandled "
"kind of index".format(
ffi.string(child.name),
ffi.string(self.node.name)))
return child
@property
def implied(self):
"""Is the last index implied? An implied index is an index whose size
is not fixed but who is not prefixed by its size because this
is the last index of a table.
:return: `True` if and only if the last index is implied.
"""
child = self._row
if child.implied:
return True
return False
@property
def index(self):
"""Get indexes for a table. The indexes are used to locate a precise
row in a table. They are a subset of the table columns.
:return: The list of indexes (as :class:`Column` instances) of
the table.
"""
child = self._row
lindex = []
element = _smi.smiGetFirstElement(child)
while element != ffi.NULL:
nelement = _smi.smiGetElementNode(element)
if nelement == ffi.NULL:
raise SMIException("cannot get index "
"associated with {0}".format(
ffi.string(self.node.name)))
if nelement.nodekind != _smi.SMI_NODEKIND_COLUMN:
raise SMIException("index {0} for {1} is "
"not a column".format(
ffi.string(nelement.name),
ffi.string(self.node.name)))
lindex.append(Column(nelement))
element = _smi.smiGetNextElement(element)
return lindex
class Column(Node):
"""MIB column node. This class represent a column of a table."""
@property
def table(self):
"""Get table associated with this column.
:return: The :class:`Table` instance associated to this
column.
"""
parent = _smi.smiGetParentNode(self.node)
if parent == ffi.NULL:
raise SMIException("unable to get parent of {0}".format(
ffi.string(self.node.name)))
if parent.nodekind != _smi.SMI_NODEKIND_ROW:
raise SMIException("parent {0} of {1} is not a row".format(
ffi.string(parent.name),
ffi.string(self.node.name)))
parent = _smi.smiGetParentNode(parent)
if parent == ffi.NULL:
raise SMIException("unable to get parent of {0}".format(
ffi.string(self.node.name)))
if parent.nodekind != _smi.SMI_NODEKIND_TABLE:
raise SMIException("parent {0} of {1} is not a table".format(
ffi.string(parent.name),
ffi.string(self.node.name)))
t = Table(parent)
return t
def reset():
"""Reset libsmi to its initial state."""
_smi.smiExit()
if _smi.smiInit(b"snimpy") < 0:
raise SMIException("unable to init libsmi")
_smi.smiSetErrorLevel(0)
try:
_smi.smiSetFlags(_smi.SMI_FLAG_ERRORS | _smi.SMI_FLAG_RECURSIVE)
except TypeError:
pass # We are being mocked
def _get_module(name):
"""Get the SMI module from its name.
:param name: The name of the module
:return: The SMI module or `None` if not found (not loaded)
"""
if not isinstance(name, bytes):
name = name.encode("ascii")
m = _smi.smiGetModule(name)
if m == ffi.NULL:
return None
if m.conformance and m.conformance <= 1:
return None
return m
def _kind2object(kind):
return {
_smi.SMI_NODEKIND_NODE: Node,
_smi.SMI_NODEKIND_SCALAR: Scalar,
_smi.SMI_NODEKIND_TABLE: Table,
_smi.SMI_NODEKIND_COLUMN: Column
}.get(kind, Node)
def get(mib, name):
"""Get a node by its name.
:param mib: The MIB name to query
:param name: The object name to get from the MIB
:return: the requested MIB node (:class:`Node`)
"""
if not isinstance(mib, bytes):
mib = mib.encode("ascii")
module = _get_module(mib)
if module is None:
raise SMIException("no module named {0}".format(mib))
node = _smi.smiGetNode(module, name.encode("ascii"))
if node == ffi.NULL:
raise SMIException("in {0}, no node named {1}".format(
mib, name))
pnode = _kind2object(node.nodekind)
return pnode(node)
def _get_kind(mib, kind):
"""Get nodes of a given kind from a MIB.
:param mib: The MIB name to search objects for
:param kind: The SMI kind of object
:return: The list of matched MIB nodes for the MIB
"""
if not isinstance(mib, bytes):
mib = mib.encode("ascii")
module = _get_module(mib)
if module is None:
raise SMIException("no module named {0}".format(mib))
lnode = []
node = _smi.smiGetFirstNode(module, kind)
while node != ffi.NULL:
lnode.append(_kind2object(kind)(node))
node = _smi.smiGetNextNode(node, kind)
return lnode
def getNodes(mib):
"""Return all nodes from a given MIB.
:param mib: The MIB name
:return: The list of all MIB nodes for the MIB
:rtype: list of :class:`Node` instances
"""
return _get_kind(mib, _smi.SMI_NODEKIND_NODE)
def getScalars(mib):
"""Return all scalars from a given MIB.
:param mib: The MIB name
:return: The list of all scalars for the MIB
:rtype: list of :class:`Scalar` instances
"""
return _get_kind(mib, _smi.SMI_NODEKIND_SCALAR)
def getTables(mib):
"""Return all tables from a given MIB.
:param mib: The MIB name
:return: The list of all tables for the MIB
:rtype: list of :class:`Table` instances
"""
return _get_kind(mib, _smi.SMI_NODEKIND_TABLE)
def getColumns(mib):
"""Return all columns from a givem MIB.
:param mib: The MIB name
:return: The list of all columns for the MIB
:rtype: list of :class:`Column` instances
"""
return _get_kind(mib, _smi.SMI_NODEKIND_COLUMN)
def load(mib):
"""Load a MIB into the library.
:param mib: The MIB to load, either a filename or a MIB name.
:return: The MIB name that has been loaded.
:except SMIException: The requested MIB cannot be loaded.
"""
if not isinstance(mib, bytes):
mib = mib.encode("ascii")
modulename = _smi.smiLoadModule(mib)
if modulename == ffi.NULL:
raise SMIException("unable to load {0}".format(mib))
modulename = ffi.string(modulename)
if not _get_module(modulename.decode("ascii")):
raise SMIException("{0} contains major SMI error "
"(check with smilint -s -l1)".format(mib))
return modulename
reset()
snimpy-0.8.1/snimpy/manager.py 0000644 0001750 0001750 00000033605 12230342274 017120 0 ustar bernat bernat 0000000 0000000 #
# snimpy -- Interactive SNMP tool
#
# Copyright (C) Vincent Bernat
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""This module is the high-level interface to *Snimpy*. It exposes
:class:`Manager` class to instantiate a new manager (which is an SNMP
client). This is the preferred interface for *Snimpy*.
Here is a simple example of use of this module::
>>> load("IF-MIB")
>>> m = Manager("localhost")
>>> m.ifDescr[1]
"""
from time import time
from collections import MutableMapping
from snimpy import snmp, mib, basictypes
class DelegatedSession(object):
"""General class for SNMP session for delegation"""
def __init__(self, session):
self._session = session
def __getattr__(self, attr):
return getattr(self._session, attr)
def __setattribute__(self, attr, value):
return setattr(self._session, attr, value)
class DelayedSetSession(DelegatedSession):
"""SNMP session that is able to delay SET requests.
This is an adapter. The constructor takes the original (not
delayed) session.
"""
def __init__(self, session):
DelegatedSession.__init__(self, session)
self.setters = []
def set(self, *args):
self.setters.extend(args)
def commit(self):
if self.setters:
self._session.set(*self.setters)
class NoneSession(DelegatedSession):
"""SNMP session that will return None on unsucessful GET requests.
In a normal session, a GET request returning `No such instance`
error will trigger an exception. This session will catch such an
error and return None instead.
"""
def get(self, *args):
try:
return self._session.get(*args)
except (snmp.SNMPNoSuchName,
snmp.SNMPNoSuchObject,
snmp.SNMPNoSuchInstance):
if len(args) > 1:
# We can't handle this case yet because we don't know
# which value is unavailable.
raise
return ((args[0], None),)
class CachedSession(DelegatedSession):
"""SNMP session using a cache.
This is an adapter. The constructor takes the original session.
"""
def __init__(self, session, timeout=5):
DelegatedSession.__init__(self, session)
self.cache = {}
self.timeout = timeout
self.count = 0
def getorgetnext(self, op, *args):
self.count += 1
if (op, args) in self.cache:
t, v = self.cache[op, args]
if time() - t < self.timeout:
return v
value = getattr(self._session, op)(*args)
self.cache[op, args] = [time(), value]
self.flush()
return value
def get(self, *args):
return self.getorgetnext("get", *args)
def getnext(self, *args):
return self.getorgetnext("getnext", *args)
def flush(self):
if self.count < 1000:
return
keys = self.cache.keys()
for k in keys:
if time() - self.cache[k][0] > self.timeout:
del self.cache[k]
self.count = 0
class Manager(object):
"""SNMP manager. An instance of this class will represent an SNMP
manager (client).
When a MIB is loaded with :func:`load`, scalars and row names from
it will be made available as an instance attribute. For a scalar,
reading the corresponding attribute will get its value while
setting it will allow to modify it:
>>> load("SNMPv2-MIB")
>>> m = Manager("localhost", "private")
>>> m.sysContact
>>> m.sysContact = "Brian Jones"
>>> m.sysContact
For a row name, the provided interface is like a Python
dictionary. Requesting an item using its index will retrieve the
value from the agent (the server)::
>>> load("IF-MIB")
>>> m = Manager("localhost", "private")
>>> m.ifDescr[1]
>>> m.ifName[1] = "Loopback interface"
Also, it is possible to iterate on a row name to get all available
values for index::
>>> load("IF-MIB")
>>> m = Manager("localhost", "private")
>>> for idx in m.ifDescr:
... print(m.ifDescr[idx])
A context manager is also provided. Any modification issued inside
the context will be delayed until the end of the context and then
grouped into a single SNMP PDU to be executed atomically::
>>> load("IF-MIB")
>>> m = Manager("localhost", "private")
>>> with m:
... m.ifName[1] = "Loopback interface"
... m.ifName[2] = "First interface"
Any error will be turned into an exception::
>>> load("IF-MIB")
>>> m = Manager("localhost", "private")
>>> m.ifDescr[999]
Traceback (most recent call last):
...
SNMPNoSuchName: There is no such variable name in this MIB.
"""
# do we want this object to be populated with all nodes?
_complete = False
def __init__(self,
host="localhost",
community="public", version=2,
cache=False, none=False,
timeout=None, retries=None,
# SNMPv3
secname=None,
authprotocol=None, authpassword=None,
privprotocol=None, privpassword=None):
"""Create a new SNMP manager. Some of the parameters are explained in
:meth:`snmp.Session.__init__`.
:param host: The hostname or IP address of the agent to
connect to. Optionally, the port can be specified
separated with a double colon.
:type host: str
:param community: The community to transmit to the agent for
authorization purpose. This parameter is ignored if the
specified version is 3.
:type community: str
:param version: The SNMP version to use to talk with the
agent. Possible values are `1`, `2` (community-based) or
`3`.
:type version: int
:param cache: Should caching be enabled? This can be either a
boolean or an integer to specify the cache timeout in
seconds. If `True`, the default timeout is 5 seconds.
:type cache: bool or int
:param none: Should `None` be returned when the agent does not
know the requested OID? If `True`, `None` will be returned
when requesting an inexisting scalar or column.
:type none: bool
:param timeout: Use the specified value in seconds as timeout.
:type timeout: int
:param retries: How many times the request should be retried?
:type retries: int
"""
if host is None:
host = Manager._host
self._host = host
self._session = snmp.Session(host, community, version,
secname,
authprotocol, authpassword,
privprotocol, privpassword)
if timeout is not None:
self._session.timeout = int(timeout * 1000000)
if retries is not None:
self._session.retries = retries
if cache:
if cache is True:
self._session = CachedSession(self._session)
else:
self._session = CachedSession(self._session, cache)
if none:
self._session = NoneSession(self._session)
def _locate(self, attribute):
for m in loaded:
try:
a = mib.get(m, attribute)
return (m, a)
except mib.SMIException:
pass
raise AttributeError("{0} is not an attribute".format(attribute))
def __getattribute__(self, attribute):
if attribute.startswith("_"):
return object.__getattribute__(self, attribute)
m, a = self._locate(attribute)
if isinstance(a, mib.Scalar):
oid, result = self._session.get(a.oid + (0,))[0]
if result is not None:
return a.type(a, result)
return None
elif isinstance(a, mib.Column):
return ProxyColumn(self._session, a)
raise NotImplementedError
def __setattr__(self, attribute, value):
if attribute.startswith("_"):
return object.__setattr__(self, attribute, value)
m, a = self._locate(attribute)
if not isinstance(value, basictypes.Type):
value = a.type(a, value, raw=False)
if isinstance(a, mib.Scalar):
self._session.set(a.oid + (0,), value)
return
raise AttributeError("{0} is not writable".format(attribute))
def __repr__(self):
return "".format(self._host)
def __enter__(self):
"""In a context, we group all "set" into a single request"""
self._osession = self._session
self._session = DelayedSetSession(self._session)
return self
def __exit__(self, type, value, traceback):
"""When we exit, we should execute all "set" requests"""
try:
if type is None:
self._session.commit()
finally:
self._session = self._osession
del self._osession
class Proxy:
def __repr__(self):
return "<{0} for {1}>".format(self.__class__.__name__,
repr(self.proxy)[1:-1])
class ProxyColumn(Proxy, MutableMapping):
"""Proxy for column access"""
def __init__(self, session, column):
self.proxy = column
self.session = session
def _op(self, op, index, *args):
if not isinstance(index, tuple):
index = (index,)
indextype = self.proxy.table.index
if len(indextype) != len(index):
raise ValueError(
"{0} column uses the following "
"indexes: {1!r}".format(self.proxy, indextype))
oidindex = []
for i, ind in enumerate(index):
# Cast to the correct type since we need "toOid()"
ind = indextype[i].type(indextype[i], ind, raw=False)
oidindex.extend(ind.toOid())
result = getattr(
self.session,
op)(self.proxy.oid + tuple(oidindex),
*args)
if op != "set":
oid, result = result[0]
if result is not None:
return self.proxy.type(self.proxy, result)
return None
def __getitem__(self, index):
return self._op("get", index)
def __setitem__(self, index, value):
if not isinstance(value, basictypes.Type):
value = self.proxy.type(self.proxy, value, raw=False)
self._op("set", index, value)
def __delitem__(self, index):
raise NotImplementedError("cannot suppress a column")
def keys(self):
return [k for k in self]
def has_key(self, object):
try:
self._op("get", object)
except:
return False
return True
def __iter__(self):
for k, _ in self.iteritems():
yield k
def __len__(self):
len(list(self.iteritems()))
def iteritems(self):
count = 0
oid = self.proxy.oid
indexes = self.proxy.table.index
for noid, result in self.session.walk(oid):
if noid <= oid:
noid = None
break
oid = noid
if not((len(oid) >= len(self.proxy.oid) and
oid[:len(self.proxy.oid)] ==
self.proxy.oid[:len(self.proxy.oid)])):
noid = None
break
# oid should be turned into index
index = oid[len(self.proxy.oid):]
target = []
for x in indexes:
l, o = x.type.fromOid(x, tuple(index))
target.append(x.type(x, o))
index = index[l:]
count = count + 1
if len(target) == 1:
# Should work most of the time
yield target[0], result
else:
yield tuple(target), result
if count == 0:
# We did not find any element. Is it because the column is
# empty or because the column does not exist. We do a SNMP
# GET to know. If we get a "No such instance" exception,
# this means the column is empty. If we get "No such
# object", this means the column does not exist. We cannot
# make such a distinction with SNMPv1.
try:
self.session.get(self.proxy.oid)
except snmp.SNMPNoSuchInstance:
# OK, the set of result is really empty
raise StopIteration
except snmp.SNMPNoSuchName:
# SNMPv1, we don't know
pass
except snmp.SNMPNoSuchObject:
# The result is empty because the column is unknown
raise
raise StopIteration
loaded = []
def load(mibname):
"""Load a MIB in memory.
:param mibname: MIB name or filename
:type mibname: str
"""
m = mib.load(mibname)
if m not in loaded:
loaded.append(m)
if Manager._complete:
for o in mib.getScalars(m) + \
mib.getColumns(m):
setattr(Manager, str(o), 1)
snimpy-0.8.1/snimpy/__main__.py 0000644 0001750 0001750 00000001611 12225620541 017216 0 ustar bernat bernat 0000000 0000000 #
# snimpy -- Interactive SNMP tool
#
# Copyright (C) Vincent Bernat
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
if __name__ == "__main__": # pragma: no cover
from snimpy import main
main.interact()
snimpy-0.8.1/snimpy/main.py 0000644 0001750 0001750 00000010510 12225620542 016421 0 ustar bernat bernat 0000000 0000000 #
# snimpy -- Interactive SNMP tool
#
# Copyright (C) Vincent Bernat
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# We are using readline module of Python. Depending on the Python
# distribution, this module may be linked to GNU Readline which is
# GPLv2 licensed.
"""Provide an interactive shell for snimpy.
The main method is C{interact()}. It will either use IPython if
available or just plain python Shell otherwise. It will also try to
use readline if available.
For IPython, there is some configuration stuff to use a special
profile. This is the recommended way to use it. It allows a separate
history.
"""
import sys
import os
import atexit
import code
from datetime import timedelta
import snimpy
from snimpy import manager
from snimpy.config import conf
def interact(argv=sys.argv): # pragma: no cover
banner = "\033[1mSnimpy\033[0m ({0}) -- {1}.\n".format(
snimpy.__version__, snimpy.__doc__)
banner += " load -> load an additional MIB\n"
banner += " M -> manager object"
local = {"conf": conf,
"M": manager.Manager,
"load": manager.load,
"timedelta": timedelta,
"snmp": manager.snmp
}
if len(argv) <= 1:
manager.Manager._complete = True
for ms in conf.mibs:
manager.load(ms)
globals().update(local)
if len(argv) > 1:
argv = argv[1:]
exec(compile(open(argv[0]).read(), argv[0], 'exec')) in local
return
try:
import rlcompleter
import readline
except ImportError:
readline = None
try:
try:
# ipython >= 0.11
from IPython.frontend.terminal.embed import InteractiveShellEmbed
from IPython.config.loader import Config
cfg = Config()
try:
# >= 0.12
cfg.PromptManager.in_template = "Snimpy [\\#]> "
cfg.PromptManager.out_template = "Snimpy [\\#]: "
except ImportError:
# 0.11
cfg.InteractiveShellEmbed.prompt_in1 = "Snimpy [\\#]> "
cfg.InteractiveShellEmbed.prompt_out = "Snimpy [\\#]: "
if conf.ipythonprofile:
cfg.InteractiveShellEmbed.profile = conf.ipythonprofile
shell = InteractiveShellEmbed(
config=cfg,
banner1=banner,
user_ns=local)
# Not interested by traceback in this module
shell.InteractiveTB.tb_offset += 1
except ImportError:
# ipython < 0.11
from IPython.Shell import IPShellEmbed
argv = ["-prompt_in1", "Snimpy [\\#]> ",
"-prompt_out", "Snimpy [\\#]: "]
if conf.ipythonprofile:
argv += ["-profile", conf.ipythonprofile]
shell = IPShellEmbed(argv=argv,
banner=banner, user_ns=local)
# Not interested by traceback in this module
shell.IP.InteractiveTB.tb_offset += 1
except ImportError:
shell = None
if shell and conf.ipython:
shell()
else:
if readline:
if conf.histfile:
try:
readline.read_history_file(
os.path.expanduser(conf.histfile))
except IOError:
pass
atexit.register(lambda: readline.write_history_file(
os.path.expanduser(conf.histfile)))
readline.set_completer(rlcompleter.Completer(local).complete)
readline.parse_and_bind("tab: menu-complete")
sys.ps1 = conf.prompt
code.interact(banner=banner, local=local)
snimpy-0.8.1/snimpy/__init__.py 0000644 0001750 0001750 00000000157 12232211704 017234 0 ustar bernat bernat 0000000 0000000 """interactive SNMP tool"""
__author__ = 'Vincent Bernat'
__email__ = 'bernat@luffy.cx'
__version__ = '0.8.1'
snimpy-0.8.1/snimpy/cffi_fix.py 0000644 0001750 0001750 00000003727 12232166465 017275 0 ustar bernat bernat 0000000 0000000 # Copyright 2013 Donald Stufft and individual contributors
# Copyright 2013 Vincent Bernat
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The original version is available here:
# https://github.com/pyca/pynacl/blob/master/src/nacl/_cffi_fix.py
#
# This modified version will just call original implementation but
# with additional suffixes if needed.
import imp
import sys
import functools
def get_so_suffixes():
suffixes = [suffix
for suffix, _, t in imp.get_suffixes()
if t == imp.C_EXTENSION]
if not suffixes:
# bah, no C_EXTENSION available. Occurs on pypy without cpyext
if sys.platform == 'win32':
suffixes = [".pyd"]
else:
suffixes = [".so"]
return suffixes
def patch(cls):
"""Patch `find_module` method to try more suffixes."""
original_find_module = cls.find_module
@functools.wraps(original_find_module)
def find_more_modules(self, module_name, path, so_suffix):
suffixes = get_so_suffixes()
if so_suffix in suffixes:
suffixes.remove(so_suffix)
suffixes.insert(0, so_suffix)
for suffix in suffixes:
filename = original_find_module(self, module_name, path, suffix)
if filename is not None:
return filename
return None
cls.find_module = find_more_modules
import cffi.vengine_cpy
import cffi.vengine_gen
patch(cffi.vengine_cpy.VCPythonEngine)
patch(cffi.vengine_gen.VGenericEngine)
snimpy-0.8.1/snimpy/basictypes.py 0000644 0001750 0001750 00000073146 12230342274 017660 0 ustar bernat bernat 0000000 0000000 #
# snimpy -- Interactive SNMP tool
#
# Copyright (C) Vincent Bernat
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""
This module is aimed at providing Pythonic representation of
various SNMP types. Each SMIv2 type is mapped to a corresponding class
which tries to mimic a basic type from Python. For example, display
strings are like Python string while SMIv2 integers are just like
Python integers. This module is some kind of a hack and its use
outside of *Snimpy* seems convoluted.
"""
import sys
import struct
import socket
import re
from datetime import timedelta
from pysnmp.proto import rfc1902
from snimpy import mib
PYTHON3 = sys.version_info >= (3, 0)
if PYTHON3:
ord2 = lambda x: x
chr2 = lambda x: bytes([x])
unicode = str
long = int
else:
ord2 = ord
chr2 = chr
def ordering_with_cmp(cls):
ops = {'__lt__': lambda self, other: self.__cmp__(other) < 0,
'__gt__': lambda self, other: self.__cmp__(other) > 0,
'__le__': lambda self, other: self.__cmp__(other) <= 0,
'__ge__': lambda self, other: self.__cmp__(other) >= 0,
'__eq__': lambda self, other: self.__cmp__(other) == 0,
'__ne__': lambda self, other: self.__cmp__(other) != 0}
for opname, opfunc in ops.items():
opfunc.__name__ = opname
opfunc.__doc__ = getattr(int, opname).__doc__
setattr(cls, opname, opfunc)
return cls
class Type(object):
"""Base class for all types."""
def __new__(cls, entity, value, raw=True):
"""Create a new typed value.
:param entity: A :class:`mib.Node` instance
:param value: The value to set
:param raw: Whetever the raw value is provided (as opposed to
a user-supplied value). This parameter is important when
the provided input is ambiguous, for example when it is an
array of bytes.
:type raw: bool
:return: an instance of the new typed value
"""
if entity.type != cls:
raise ValueError("MIB node is {0}. We are {1}".format(entity.type,
cls))
if cls == OctetString and entity.fmt is not None:
# Promotion of OctetString to String if we have unicode stuff
if isinstance(value, (String, unicode)) or not raw:
cls = String
if not isinstance(value, Type):
value = cls._internal(entity, value)
else:
value = cls._internal(entity, value._value)
if issubclass(cls, unicode):
self = unicode.__new__(cls, value)
elif issubclass(cls, bytes):
self = bytes.__new__(cls, value)
elif issubclass(cls, long):
self = long.__new__(cls, value)
else:
self = object.__new__(cls)
self._value = value
self.entity = entity
if cls == OctetString and entity.fmt is not None:
# A display-hint propose to use only ascii and UTF-8
# chars. We promote an OCTET-STRING to a DisplayString if
# we have a format. This means we won't be able to access
# individual bytes in this format, only the full displayed
# version.
value = String._internal(entity, self)
self = unicode.__new__(String, value)
self._value = value
self.entity = entity
return self
@classmethod
def _internal(cls, entity, value):
"""Get internal value for a given value."""
raise NotImplementedError # pragma: no cover
def pack(self):
"""Prepare the instance to be sent on the wire."""
raise NotImplementedError # pragma: no cover
def toOid(self):
"""Convert to an OID.
If this function is implemented, then class function
:meth:`fromOid` should also be implemented as the "invert"
function of this one.
This function only works if the entity is used as an index!
Otherwise, it should raises NotImplementedError.
:return: An OID that can be used as index
"""
raise NotImplementedError # pragma: no cover
@classmethod
def fromOid(cls, entity, oid):
"""Create instance from an OID.
This is the sister function of :meth:`toOid`.
:param oid: The OID to use to create an instance
:param entity: The MIB entity we want to instantiate
:return: A couple `(l, v)` with `l` the number of suboids
needed to create the instance and `v` the instance created from
the OID
"""
raise NotImplementedError # pragma: no cover
@classmethod
def _fixedOrImplied(cls, entity):
"""Determine if the given entity is fixed-len or implied.
This function is an helper that is used for String and
Oid. When converting a variable-length type to an OID, we need
to prefix it by its len or not depending of what the MIB say.
:param entity: entity to check
:return: "fixed" if it is fixed-len, "implied" if implied var-len,
`False` otherwise
"""
if entity.ranges and not isinstance(entity.ranges, (tuple, list)):
# Fixed length
return "fixed"
# We have a variable-len string/oid. We need to know if it is implied.
try:
table = entity.table
except:
raise NotImplementedError(
"{0} is not an index of a table".format(entity))
indexes = [str(a) for a in table.index]
if str(entity) not in indexes:
raise NotImplementedError(
"{0} is not an index of a table".format(entity))
if str(entity) != indexes[-1] or not table.implied:
# This index is not implied
return False
return "implied"
def __str__(self):
return str(self._value)
def __repr__(self):
try:
return '<{0}: {1}>'.format(self.__class__.__name__,
str(self))
except:
return '<{0} ????>'.format(self.__class__.__name__)
@ordering_with_cmp
class IpAddress(Type):
"""Class representing an IP address/"""
@classmethod
def _internal(cls, entity, value):
if isinstance(value, (list, tuple)):
value = ".".join([str(a) for a in value])
elif isinstance(value, bytes):
try:
value = socket.inet_ntoa(value)
except:
pass
try:
value = socket.inet_ntoa(socket.inet_aton(value))
except:
raise ValueError("{0!r} is not a valid IP".format(value))
return [int(a) for a in value.split(".")]
def pack(self):
return (
rfc1902.IpAddress(
str(".".join(["{0:d}".format(x) for x in self._value])))
)
def toOid(self):
return tuple(self._value)
@classmethod
def fromOid(cls, entity, oid):
if len(oid) < 4:
raise ValueError(
"{0!r} is too short for an IP address".format(oid))
return (4, cls(entity, oid[:4]))
def __str__(self):
return ".".join([str(a) for a in self._value])
def __cmp__(self, other):
if not isinstance(other, IpAddress):
try:
other = IpAddress(self.entity, other)
except:
raise NotImplementedError # pragma: no cover
if self._value == other._value:
return 0
if self._value < other._value:
return -1
return 1
def __getitem__(self, nb):
return self._value[nb]
class StringOrOctetString(Type):
def toOid(self):
# To convert properly to OID, we need to know if it is a
# fixed-len string, an implied string or a variable-len
# string.
b = self._toBytes()
if self._fixedOrImplied(self.entity):
return tuple(ord2(a) for a in b)
return tuple([len(b)] + [ord2(a) for a in b])
def _toBytes(self):
raise NotImplementedError
@classmethod
def fromOid(cls, entity, oid):
type = cls._fixedOrImplied(entity)
if type == "implied":
# Eat everything
return (len(oid), cls(entity, b"".join([chr2(x) for x in oid])))
if type == "fixed":
l = entity.ranges
if len(oid) < l:
raise ValueError(
"{0} is too short for wanted fixed "
"string (need at least {1:d})".format(oid, l))
return (l, cls(entity, b"".join([chr2(x) for x in oid[:l]])))
# This is var-len
if not oid:
raise ValueError("empty OID while waiting for var-len string")
l = oid[0]
if len(oid) < l + 1:
raise ValueError(
"{0} is too short for variable-len "
"string (need at least {1:d})".format(oid, l))
return (
(l + 1, cls(entity, b"".join([chr2(x) for x in oid[1:(l + 1)]])))
)
def pack(self):
return rfc1902.OctetString(self._toBytes())
class OctetString(StringOrOctetString, bytes):
"""Class for a generic octet string. This class should be compared to
:class:`String` which is used to represent a display string. This
class is usually used to store raw bytes, like a bitmask of
VLANs.
"""
@classmethod
def _internal(cls, entity, value):
# Internally, we are using bytes
if isinstance(value, bytes):
return value
if isinstance(value, unicode):
return value.encode("ascii")
return bytes(value)
def _toBytes(self):
return self._value
def __ior__(self, value):
nvalue = [ord2(u) for u in self._value]
if not isinstance(value, (tuple, list)):
value = [value]
for v in value:
if not isinstance(v, (int, long)):
raise NotImplementedError(
"on string, bit-operation are limited to integers")
if len(nvalue) < (v >> 3) + 1:
nvalue.extend([0] * ((v >> 3) + 1 - len(self._value)))
nvalue[v >> 3] |= 1 << (7 - v % 8)
return self.__class__(self.entity, b"".join([chr2(i) for i in nvalue]))
def __isub__(self, value):
nvalue = [ord2(u) for u in self._value]
if not isinstance(value, (tuple, list)):
value = [value]
for v in value:
if not isinstance(v, int) and not isinstance(v, long):
raise NotImplementedError(
"on string, bit-operation are limited to integers")
if len(nvalue) < (v >> 3) + 1:
continue
nvalue[v >> 3] &= ~(1 << (7 - v % 8))
return self.__class__(self.entity, b"".join([chr2(i) for i in nvalue]))
return self
def __and__(self, value):
nvalue = [ord2(u) for u in self._value]
if not isinstance(value, (tuple, list)):
value = [value]
for v in value:
if not isinstance(v, (int, long)):
raise NotImplementedError(
"on string, bit-operation are limited to integers")
if len(nvalue) < (v >> 3) + 1:
return False
if not(nvalue[v >> 3] & (1 << (7 - v % 8))):
return False
return True
class String(StringOrOctetString, unicode):
"""Class for a display string. Such a string is an unicode string and
it is therefore expected that only printable characters are
used. This is usually the case if the corresponding MIB node comes
with a format string.
With such an instance, the user is expected to be able to provide
a formatted. For example, a MAC address could be written
`00:11:22:33:44:55`.
"""
@classmethod
def _parseOctetFormat(cls, fmt, j):
# repeater
if fmt[j] == "*":
dorepeat = True
j += 1
else:
dorepeat = False
# length
length = ""
while fmt[j].isdigit():
length += fmt[j]
j += 1
length = int(length)
# format
format = fmt[j]
j += 1
# seperator
if j < len(fmt) and \
fmt[j] != "*" and not fmt[j].isdigit():
sep = fmt[j]
j += 1
else:
sep = ""
# terminator
if j < len(fmt) and \
fmt[j] != "*" and not fmt[j].isdigit():
term = fmt[j]
j += 1
else:
term = ""
return (j, dorepeat, length, format, sep, term)
@classmethod
def _fromBytes(cls, value, fmt):
i = 0 # Position in value
j = 0 # Position in fmt
result = ""
while i < len(value):
if j < len(fmt):
j, dorepeat, length, format, sep, term = cls._parseOctetFormat(
fmt, j)
# building
if dorepeat:
repeat = ord2(value[i])
i += 1
else:
repeat = 1
for r in range(repeat):
bb = value[i:i + length]
i += length
if format in ['o', 'x', 'd']:
if length > 4:
raise ValueError(
"don't know how to handle integers "
"more than 4 bytes long")
bb = b"\x00" * (4 - length) + bb
number = struct.unpack(b"!l", bb)[0]
if format == "o":
# In Python2, oct() is 01242, while it is 0o1242 in
# Python3
result += "".join(oct(number).partition("o")[0:3:2])
elif format == "x":
result += hex(number)[2:]
else: # format == "d":
result += str(number)
elif format == "a":
result += bb.decode("ascii")
elif format == "t":
result += bb.decode("utf-8")
else:
raise ValueError("{0!r} cannot be represented with "
"the given display string ({1})".format(
bb, fmt))
result += sep
if sep and term:
result = result[:-1]
result += term
if term or sep:
result = result[:-1]
return result
def _toBytes(self):
# We need to reverse what was done by `_fromBytes`. This is
# not an exact science. In most case, this is easy because a
# separator is used but sometimes, this is not. We do some
# black magic that will fail.
i = 0
j = 0
fmt = self.entity.fmt
bb = b""
while i < len(self._value):
if j < len(fmt):
parsed = self._parseOctetFormat(fmt, j)
j, dorepeat, length, format, sep, term = parsed
if format == "o":
fmatch = "(?P[0-7]{{1,{0}}})".format(
int(length * 2.66667) + 1)
elif format == "x":
fmatch = "(?P[0-9A-Fa-f]{{1,{0}}})".format(length * 2)
elif format == "d":
fmatch = "(?P[0-9]{{1,{0}}})".format(
int(length * 2.4083) + 1)
elif format == "a":
fmatch = "(?P.{{1,{0}}})".format(length)
elif format == "t":
fmatch = "(?P.{{1,{0}}})".format(length)
else:
raise ValueError("{0!r} cannot be parsed due to an "
"incorrect format ({1})".format(
self._value, fmt))
repeats = []
while True:
mo = re.match(fmatch, self._value[i:])
if not mo:
raise ValueError("{0!r} cannot be parsed because it "
"does not match format {1} at "
"index {i}".format(self._value, fmt, i))
if format in ["o", "x", "d"]:
if format == "o":
r = int(mo.group("o"), 8)
elif format == "x":
r = int(mo.group("x"), 16)
else:
r = int(mo.group("d"))
result = struct.pack(b"!l", r)[-length:]
else:
result = mo.group(1).encode("utf-8")
i += len(mo.group(1))
if dorepeat:
repeats.append(result)
if i < len(self._value):
# Approximate...
if sep and self._value[i] == sep:
i += 1
elif term and self._value[i] == term:
i += 1
break
else:
break
else:
break
if dorepeat:
bb += chr2(len(repeats))
bb += b"".join(repeats)
else:
bb += result
if i < len(self._value) and (sep and self._value[i] == sep or
term and self._value[i] == term):
i += 1
return bb
@classmethod
def _internal(cls, entity, value):
# Internally, we use the displayed string. We have a special
# case if the value is an OctetString to do the conversion.
if isinstance(value, OctetString):
return cls._fromBytes(value._value, entity.fmt)
if PYTHON3 and isinstance(value, bytes):
return value.decode("utf-8")
return unicode(value)
def __str__(self):
return self._value
class Integer(Type, long):
"""Class for any integer."""
@classmethod
def _internal(cls, entity, value):
return long(value)
def pack(self):
if self._value >= (1 << 64):
raise OverflowError("too large to be packed")
if self._value >= (1 << 32):
return rfc1902.Counter64(self._value)
if self._value >= 0:
return rfc1902.Integer(self._value)
if self._value >= -(1 << 31):
return rfc1902.Integer(self._value)
raise OverflowError("too small to be packed")
def toOid(self):
return (self._value,)
@classmethod
def fromOid(cls, entity, oid):
if len(oid) < 1:
raise ValueError("{0} is too short for an integer".format(oid))
return (1, cls(entity, oid[0]))
def __str__(self):
if self.entity.fmt:
if self.entity.fmt[0] == "x":
return hex(self._value)
if self.entity.fmt[0] == "o":
return oct(self._value)
if self.entity.fmt[0] == "b":
if self._value == 0:
return "0"
if self._value > 0:
v = self._value
r = ""
while v > 0:
r = str(v % 2) + r
v = v >> 1
return r
elif self.entity.fmt[0] == "d" and \
len(self.entity.fmt) > 2 and \
self.entity.fmt[1] == "-":
dec = int(self.entity.fmt[2:])
result = str(self._value)
if len(result) < dec + 1:
result = "0" * (dec + 1 - len(result)) + result
return "{0}.{1}".format(result[:-2], result[-2:])
return str(self._value)
class Unsigned32(Integer):
"""Class to represent an unsigned 32bits integer."""
def pack(self):
if self._value >= (1 << 32):
raise OverflowError("too large to be packed")
if self._value < 0:
raise OverflowError("too small to be packed")
return rfc1902.Unsigned32(self._value)
class Unsigned64(Integer):
"""Class to represent an unsigned 64bits integer."""
def pack(self):
if self._value >= (1 << 64):
raise OverflowError("too large to be packed")
if self._value < 0:
raise OverflowError("too small to be packed")
return rfc1902.Counter64(self._value)
class Enum(Integer):
"""Class for an enumeration. An enumaration is an integer but labels
are attached to some values for a more user-friendly display."""
@classmethod
def _internal(cls, entity, value):
if value in entity.enum:
return value
for (k, v) in entity.enum.items():
if (v == value):
return k
try:
return long(value)
except:
raise ValueError("{0!r} is not a valid "
"value for {1}".format(value,
entity))
def pack(self):
return rfc1902.Integer(self._value)
@classmethod
def fromOid(cls, entity, oid):
if len(oid) < 1:
raise ValueError(
"{0!r} is too short for an enumeration".format(oid))
return (1, cls(entity, oid[0]))
def __eq__(self, other):
if not isinstance(other, self.__class__):
try:
other = self.__class__(self.entity, other)
except:
raise NotImplementedError # pragma: no cover
return self._value == other._value
def __ne__(self, other):
return not(self.__eq__(other))
def __str__(self):
if self._value in self.entity.enum:
return (
"{0}({1:d})".format(self.entity.enum[self._value], self._value)
)
else:
return str(self._value)
@ordering_with_cmp
class Oid(Type):
"""Class to represent and OID."""
@classmethod
def _internal(cls, entity, value):
if isinstance(value, (list, tuple)):
return tuple([int(v) for v in value])
elif isinstance(value, str):
return tuple([ord2(i) for i in value.split(".") if i])
elif isinstance(value, mib.Node):
return tuple(value.oid)
else:
raise TypeError(
"don't know how to convert {0!r} to OID".format(value))
def pack(self):
return rfc1902.univ.ObjectIdentifier(self._value)
def toOid(self):
if self._fixedOrImplied(self.entity):
return self._value
return tuple([len(self._value)] + list(self._value))
@classmethod
def fromOid(cls, entity, oid):
if cls._fixedOrImplied(entity) == "fixed":
# A fixed OID? We don't like this. Provide a real example.
raise ValueError(
"{0!r} seems to be a fixed-len OID index. Odd.".format(entity))
if not cls._fixedOrImplied(entity):
# This index is not implied. We need the len
if len(oid) < 1:
raise ValueError(
"{0!r} is too short for a not "
"implied index".format(entity))
l = oid[0]
if len(oid) < l + 1:
raise ValueError(
"{0!r} has an incorrect size "
"(needs at least {1:d})".format(oid, l))
return (l + 1, cls(entity, oid[1:(l + 1)]))
else:
# Eat everything
return (len(oid), cls(entity, oid))
def __str__(self):
return ".".join([str(x) for x in self._value])
def __cmp__(self, other):
if not isinstance(other, Oid):
other = Oid(self.entity, other)
if tuple(self._value) == tuple(other._value):
return 0
if self._value > other._value:
return 1
return -1
def __contains__(self, item):
"""Test if item is a sub-oid of this OID"""
if not isinstance(item, Oid):
item = Oid(self.entity, item)
return tuple(item._value[:len(self._value)]) == \
tuple(self._value[:len(self._value)])
class Boolean(Enum):
"""Class for a boolean."""
@classmethod
def _internal(cls, entity, value):
if isinstance(value, bool):
if value:
return Enum._internal(entity, "true")
else:
return Enum._internal(entity, "false")
else:
return Enum._internal(entity, value)
def __nonzero__(self):
if self._value == 1:
return True
else:
return False
def __bool__(self):
return self.__nonzero__()
@ordering_with_cmp
class Timeticks(Type):
"""Class for timeticks."""
@classmethod
def _internal(cls, entity, value):
if isinstance(value, (int, long)):
# Value in centiseconds
return timedelta(0, value / 100.)
elif isinstance(value, timedelta):
return value
else:
raise TypeError(
"dunno how to handle {0!r} ({1})".format(value, type(value)))
def __int__(self):
return self._value.days * 3600 * 24 * 100 + \
self._value.seconds * 100 + \
self._value.microseconds // 10000
def toOid(self):
return (int(self),)
@classmethod
def fromOid(cls, entity, oid):
if len(oid) < 1:
raise ValueError("{0!r} is too short for a timetick".format(oid))
return (1, cls(entity, oid[0]))
def pack(self):
return rfc1902.TimeTicks(int(self))
def __str__(self):
return str(self._value)
def __cmp__(self, other):
if isinstance(other, Timeticks):
other = other._value
elif isinstance(other, (int, long)):
other = timedelta(0, other / 100.)
elif not isinstance(other, timedelta):
raise NotImplementedError(
"only compare to int or "
"timedelta, not {0}".format(type(other)))
if self._value == other:
return 0
if self._value < other:
return -1
return 1
class Bits(Type):
"""Class for bits."""
@classmethod
def _internal(cls, entity, value):
bits = set()
tryalternate = False
if isinstance(value, bytes):
for i, x in enumerate(value):
if ord2(x) == 0:
continue
for j in range(8):
if ord2(x) & (1 << (7 - j)):
if j not in entity.enum:
tryalternate = True
break
bits.add(j)
if tryalternate:
break
if not tryalternate:
return bits
else:
bits = set()
elif not isinstance(value, (tuple, list, set, frozenset)):
value = set([value])
for v in value:
found = False
if v in entity.enum:
bits.add(v)
found = True
else:
for (k, t) in entity.enum.items():
if (t == v):
bits.add(k)
found = True
break
if not found:
raise ValueError("{0!r} is not a valid bit value".format(v))
return bits
def pack(self):
string = []
for b in self._value:
if len(string) < (b >> 4) + 1:
string.extend([0] * ((b >> 4) - len(string) + 1))
string[b >> 416] |= 1 << (7 - b % 16)
return rfc1902.Bits(b"".join([chr2(x) for x in string]))
def __eq__(self, other):
if isinstance(other, str):
other = [other]
if not isinstance(other, Bits):
other = Bits(self.entity, other)
return self._value == other._value
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
result = []
for b in sorted(self._value):
result.append("{0}({1:d})".format(self.entity.enum[b], b))
return ", ".join(result)
def __and__(self, other):
if isinstance(other, str):
other = [other]
if not isinstance(other, Bits):
other = Bits(self.entity, other)
return len(self._value & other._value) > 0
def __ior__(self, other):
if isinstance(other, str):
other = [other]
if not isinstance(other, Bits):
other = Bits(self.entity, other)
self._value |= other._value
return self
def __isub__(self, other):
if isinstance(other, str):
other = [other]
if not isinstance(other, Bits):
other = Bits(self.entity, other)
self._value -= other._value
return self
def build(mibname, node, value):
"""Build a new basic type with the given value.
:param mibname: The MIB to use to locate the entity.
:param node: The node that will be attached to this type.
:param value: The initial value to set for the type.
:return: A :class:`Type` instance
"""
m = mib.get(mibname, node)
t = m.type(m, value)
return t
snimpy-0.8.1/snimpy/config.py 0000644 0001750 0001750 00000003201 12225620542 016741 0 ustar bernat bernat 0000000 0000000 #
# snimpy -- Interactive SNMP tool
#
# Copyright (C) Vincent Bernat
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
import os.path
import imp
class Conf:
prompt = "\033[1m[snimpy]>\033[0m "
histfile = "~/.snimpy_history" # Not used with IPython
userconf = "~/.snimpy.conf"
ipython = True
ipythonprofile = None # Set for example to "snimpy"
mibs = []
conf = Conf()
# Load user configuration file
if conf.userconf:
try:
conffile = open(os.path.expanduser(conf.userconf))
except (OSError, IOError):
pass
else:
try:
confuser = imp.load_module("confuser", conffile,
os.path.expanduser(conf.userconf),
("conf", 'r', imp.PY_SOURCE))
for k in confuser.__dict__:
if not k.startswith("__"):
conf.__dict__[k] = confuser.__dict__[k]
finally:
conffile.close()
snimpy-0.8.1/snimpy/snmp.py 0000644 0001750 0001750 00000027616 12230342274 016470 0 ustar bernat bernat 0000000 0000000 #
# snimpy -- Interactive SNMP tool
#
# Copyright (C) Vincent Bernat
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""
This module is a low-level interface to build SNMP requests, send
them and receive answers. It is built on top of pysnmp_ but the
exposed interface is far simpler. It is also far less complete and
there is an important dependency to the :mod:`basictypes` module for
type coercing.
.. _pysnmp: http://pysnmp.sourceforge.net/
"""
import re
import inspect
from pysnmp.entity.rfc3413.oneliner import cmdgen
from pysnmp.proto import rfc1902, rfc1905
from pysnmp.smi import error
class SNMPException(Exception):
"""SNMP related base exception. All SNMP exceptions are inherited from
this one. The inherited exceptions are named after the name of the
corresponding SNMP error.
"""
class SNMPTooBig(SNMPException):
pass
class SNMPNoSuchName(SNMPException):
pass
class SNMPBadValue(SNMPException):
pass
class SNMPReadOnly(SNMPException):
pass
# Dynamically build remaining (v2) exceptions
for name, obj in inspect.getmembers(error):
if name.endswith("Error") and \
inspect.isclass(obj) and \
issubclass(obj, error.MibOperationError) and \
obj != error.MibOperationError:
name = str("SNMP{0}".format(name[:-5]))
globals()[name] = type(name, (SNMPException,), {})
del name
del obj
class Session(object):
"""SNMP session. An instance of this object will represent an SNMP
session. From such an instance, one can get information from the
associated agent."""
def __init__(self, host,
community="public", version=2,
secname=None,
authprotocol=None,
authpassword=None,
privprotocol=None,
privpassword=None):
"""Create a new SNMP session.
:param host: The hostname or IP address of the agent to
connect to. Optionally, the port can be specified
separated with a double colon.
:type host: str
:param community: The community to transmit to the agent for
authorization purpose. This parameter is ignored if the
specified version is 3.
:type community: str
:param version: The SNMP version to use to talk with the
agent. Possible values are `1`, `2` (community-based) or
`3`.
:type version: int
:param secname: Security name to use for SNMPv3 only.
:type secname: str
:param authprotocol: Authorization protocol to use for
SNMPv3. This can be `None` or either the string `SHA` or
`MD5`.
:type authprotocol: None or str
:param authpassword: Authorization password if authorization
protocol is not `None`.
:type authpassword: str
:param privprotocol: Privacy protocol to use for SNMPv3. This
can be `None` or either the string `AES`, `AES128`,
`AES192`, `AES256` or `3DES`.
:type privprotocol: None or str
:param privpassword: Privacy password if privacy protocol is
not `None`.
:type privpassword: str
"""
self._host = host
self._version = version
self._cmdgen = cmdgen.CommandGenerator()
# Put authentication stuff in self._auth
if version in [1, 2]:
self._auth = cmdgen.CommunityData(
community, community, version - 1)
elif version == 3:
if secname is None:
secname = community
try:
authprotocol = {
None: cmdgen.usmNoAuthProtocol,
"MD5": cmdgen.usmHMACMD5AuthProtocol,
"SHA": cmdgen.usmHMACSHAAuthProtocol,
"SHA1": cmdgen.usmHMACSHAAuthProtocol
}[authprotocol]
except KeyError:
raise ValueError("{0} is not an acceptable authentication "
"protocol".format(authprotocol))
try:
privprotocol = {
None: cmdgen.usmNoPrivProtocol,
"DES": cmdgen.usmDESPrivProtocol,
"3DES": cmdgen.usm3DESEDEPrivProtocol,
"AES": cmdgen.usmAesCfb128Protocol,
"AES128": cmdgen.usmAesCfb128Protocol,
"AES192": cmdgen.usmAesCfb192Protocol,
"AES256": cmdgen.usmAesCfb256Protocol,
}[privprotocol]
except KeyError:
raise ValueError("{0} is not an acceptable privacy "
"protocol".format(privprotocol))
self._auth = cmdgen.UsmUserData(secname,
authpassword,
privpassword,
authprotocol,
privprotocol)
else:
raise ValueError("unsupported SNMP version {0}".format(version))
# Put transport stuff into self._transport
host, port = host.partition(":")[::2]
if not port:
port = 161
self._transport = cmdgen.UdpTransportTarget((host, int(port)))
# Bulk stuff
self.bulk = 40
def _check_exception(self, value):
"""Check if the given ASN1 value is an exception"""
if isinstance(value, rfc1905.NoSuchObject):
raise SNMPNoSuchObject("No such object was found") # nopep8
if isinstance(value, rfc1905.NoSuchInstance):
raise SNMPNoSuchInstance("No such instance exists") # nopep8
if isinstance(value, rfc1905.EndOfMibView):
raise SNMPEndOfMibView("End of MIB was reached") # nopep8
def _convert(self, value):
"""Convert a PySNMP value to some native Python type"""
for cl, fn in {rfc1902.Integer: int,
rfc1902.Integer32: int,
rfc1902.OctetString: bytes,
rfc1902.IpAddress: value.prettyOut,
rfc1902.Counter32: int,
rfc1902.Counter64: int,
rfc1902.Gauge32: int,
rfc1902.Unsigned32: int,
rfc1902.TimeTicks: int,
rfc1902.Bits: str,
rfc1902.univ.ObjectIdentifier: tuple}.items():
if isinstance(value, cl):
return fn(value)
self._check_exception(value)
raise NotImplementedError("unable to convert {0}".format(repr(value)))
def _op(self, cmd, *oids):
"""Apply an SNMP operation"""
errorIndication, errorStatus, errorIndex, varBinds = cmd(
self._auth, self._transport, *oids)
if errorIndication:
self._check_exception(errorIndication)
raise SNMPException(str(errorIndication))
if errorStatus:
# We try to find a builtin exception with the same message
exc = str(errorStatus.prettyPrint())
exc = re.sub(r'\W+', '', exc)
exc = "SNMP{0}".format(exc[0].upper() + exc[1:])
if str(exc) in globals():
raise globals()[exc]
raise SNMPException(errorStatus.prettyPrint())
if cmd in [self._cmdgen.getCmd, self._cmdgen.setCmd]:
results = [(tuple(name), val) for name, val in varBinds]
else:
results = [(tuple(name), val)
for row in varBinds for name, val in row]
if len(results) == 0:
if cmd not in [self._cmdgen.nextCmd, self._cmdgen.bulkCmd]:
raise SNMPException("empty answer")
# This seems to be filtered
raise SNMPEndOfMibView("no more stuff after this OID") # nopep8
return tuple([(oid, self._convert(val)) for oid, val in results])
def get(self, *oids):
"""Retrieve an OID value using GET.
:param oids: a list of OID to retrieve. An OID is a tuple.
:return: a list of tuples with the retrieved OID and the raw value.
"""
return self._op(self._cmdgen.getCmd, *oids)
def walk(self, *oids):
"""Retrieve OIDs values using GETBULK or GETNEXT. The method is called
"walk" but this is either a GETBULK or a GETNEXT. The later is
only used for SNMPv1 or if bulk has been disabled using
:meth:`bulk` property.
:param oids: a list of OID to retrieve. An OID is a tuple.
:return: a list of tuples with the retrieved OID and the raw value.
"""
if self._version == 1 or not self.bulk:
return self._op(self._cmdgen.nextCmd, *oids)
args = [0, self.bulk] + list(oids)
return self._op(self._cmdgen.bulkCmd, *args)
def set(self, *args):
"""Set an OID value using SET. This function takes an odd number of
arguments. They are working by pair. The first member is an
OID and the second one is :class:`basictypes.Type` instace
whose `pack()` method will be used to transform into the
appropriate form.
:return: a list of tuples with the retrieved OID and the raw value.
"""
if len(args) % 2 != 0:
raise ValueError("expect an even number of arguments for SET")
varbinds = zip(*[args[0::2], [v.pack() for v in args[1::2]]])
return self._op(self._cmdgen.setCmd, *varbinds)
def __repr__(self):
return "{0}(host={1},version={2})".format(
self.__class__.__name__,
self.host,
self.version)
@property
def timeout(self):
"""Get timeout value for the current session.
:return: Timeout value in microseconds.
"""
return self._transport.timeout * 1000000
@timeout.setter
def timeout(self, value):
"""Set timeout value for the current session.
:param value: Timeout value in microseconds.
"""
value = int(value)
if value <= 0:
raise ValueError("timeout is a positive integer")
self._transport.timeout = value / 1000000.
@property
def retries(self):
"""Get number of times a request is retried.
:return: Number of retries for each request.
"""
return self._transport.retries
@retries.setter
def retries(self, value):
"""Set number of times a request is retried.
:param value: Number of retries for each request.
"""
value = int(value)
if value < 0:
raise ValueError("retries is a non-negative integer")
self._transport.retries = value
@property
def bulk(self):
"""Get bulk settings.
:return: `False` if bulk is disabled or a non-negative integer
for the number of repetitions.
"""
return self._bulk
@bulk.setter
def bulk(self, value):
"""Set bulk settings.
:param value: `False` to disable bulk or a non-negative
integer for the number of allowed repetitions.
"""
if value is False:
self._bulk = False
return
value = int(value)
if value <= 0:
raise ValueError("{0} is not an appropriate value "
"for max repeater parameter".format(
value))
self._bulk = value
snimpy-0.8.1/HISTORY.rst 0000644 0001750 0001750 00000003104 12232211673 015477 0 ustar bernat bernat 0000000 0000000 .. :changelog:
History
-------
0.8.0 (2013-10-25)
++++++++++++++++++
* Workaround a problem with CFFI extension installation.
0.8.0 (2013-10-19)
++++++++++++++++++++
* Python 3.3 support. Pypy support.
* PEP8 compliant.
* Sphinx documentation.
* Octet strings with a display hint are now treated differently than
plain octet strings (unicode). Notably, they can now be set using
the displayed format (for example, for MAC addresses).
0.7.0 (2013-09-23)
++++++++++++++++++
* Major rewrite.
* SNMP support is now provided through PySNMP_.
* MIB parsing is still done with libsmi_ but through CFFI instead of a
C module.
* More unittests. Many bugfixes.
.. _PySNMP: http://pysnmp.sourceforge.net/
.. _libsmi: http://www.ibr.cs.tu-bs.de/projects/libsmi/
0.6.4 (2013-03-21)
++++++++++++++++++
* GETBULK support.
* MacAddress SMI type support.
0.6.3 (2012-04-13)
++++++++++++++++++
* Support for IPython 0.12.
* Minor bugfixes.
0.6.2 (2012-01-19)
++++++++++++++++++
* Ability to return None instead of getting an exception.
0.6.1 (2012-01-14)
++++++++++++++++++
* Thread safety and efficiency.
0.6 (2012-01-10)
++++++++++++++++++
* SNMPv3 support
0.5.1 (2011-08-07)
++++++++++++++++++
* Compatibility with IPython 0.11.
* Custom timeouts and retries.
0.5 (2010-02-03)
++++++++++++++++++
* Check conformity of loaded modules.
* Many bugfixes.
0.4 (2009-06-06)
++++++++++++++++++
* Allow to cache requests.
0.3 (2008-11-23)
++++++++++++++++++
* Provide a manual page.
* Use a context manager to group SET requests.
0.2.1 (2008-09-28)
++++++++++++++++++
* First release on PyPI.
snimpy-0.8.1/man/ 0000755 0001750 0001750 00000000000 12232211722 014354 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/man/snimpy.1 0000644 0001750 0001750 00000001767 11763715346 016013 0 ustar bernat bernat 0000000 0000000 .TH SNIMPY 1 "Oct 4, 2008"
.SH NAME
snimpy \- interactive SNMP tool with Python
.SH SYNOPSIS
.B snimpy
.RI [ options ]
.SH DESCRIPTION
This manual page documents briefly the
.B snimpy
command.
.PP
\fBsnimpy\fP is a Python-based tool providing a simple interface to build SNMP queries. This interface aims at being the most Pythonic possible: you grab scalars using attributes and columns are like dictionaries.
.PP
\fBsnimpy\fP can be used either interactively through its console
(derived from Python own console or from IPython if available) or by
writing \fBsnimpy\fP scripts which are just Python scripts with some global
variables available.
.SH OPTIONS
\fBsnimpy\fP does not take any option. If you launch it without any
argument, you will get the interactive console. Otherwise, the first
argument is the name of a script to be executed and the remaining
arguments are the arguments for this script.
.SH SEE ALSO
.nf
https://github.com/vincentbernat/snimpy
.fi
.SH AUTHOR
Vincent Bernat
snimpy-0.8.1/CONTRIBUTING.rst 0000644 0001750 0001750 00000006115 12226611646 016261 0 ustar bernat bernat 0000000 0000000 ============
Contributing
============
Contributions are welcome, and they are greatly appreciated! Every
little bit helps, and credit will always be given.
You can contribute in many ways:
Types of Contributions
----------------------
Report Bugs
~~~~~~~~~~~
Report bugs at https://github.com/vincentbernat/snimpy/issues.
If you are reporting a bug, please include:
* Your operating system name and version.
* Any details about your local setup that might be helpful in troubleshooting.
* Detailed steps to reproduce the bug.
Fix Bugs
~~~~~~~~
Look through the GitHub issues for bugs. Anything tagged with "bug"
is open to whoever wants to implement it.
Implement Features
~~~~~~~~~~~~~~~~~~
Look through the GitHub issues for features. Anything tagged with "feature"
is open to whoever wants to implement it.
Write Documentation
~~~~~~~~~~~~~~~~~~~
Snimpy could always use more documentation, whether as part of the
official Snimpy docs, in docstrings, or even on the web in blog posts,
articles, and such.
Submit Feedback
~~~~~~~~~~~~~~~
The best way to send feedback is to file an issue at https://github.com/vincentbernat/snimpy/issues.
If you are proposing a feature:
* Explain in detail how it would work.
* Keep the scope as narrow as possible, to make it easier to implement.
* Remember that this is a volunteer-driven project, and that contributions
are welcome :)
Get Started!
------------
Ready to contribute? Here's how to set up `snimpy` for local development.
1. Fork the `snimpy` repo on GitHub.
2. Clone your fork locally::
$ git clone git@github.com:your_name_here/snimpy.git
3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
$ mkvirtualenv snimpy
$ cd snimpy/
$ python setup.py develop
4. Create a branch for local development::
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
5. When you're done making changes, check that your changes pass flake8 and the
tests, including testing other Python versions with tox::
$ flake8 snimpy tests
$ python setup.py test
$ tox
To get flake8 and tox, just pip install them into your virtualenv.
6. Commit your changes and push your branch to GitHub::
$ git add .
$ git commit -m "Your detailed description of your changes."
$ git push origin name-of-your-bugfix-or-feature
7. Submit a pull request through the GitHub website.
Pull Request Guidelines
-----------------------
Before you submit a pull request, check that it meets these guidelines:
1. The pull request should include tests.
2. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring, and add the
feature to the list in README.rst.
3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check
https://travis-ci.org/vincentbernat/snimpy/pull_requests
and make sure that the tests pass for all supported Python versions.
Tips
----
To run a subset of tests::
$ python -m nose tests/test_snmp.py
snimpy-0.8.1/setup.py 0000644 0001750 0001750 00000003321 12230342274 015317 0 ustar bernat bernat 0000000 0000000 from setuptools import setup
import snimpy
try:
import multiprocessing
import pysnmp
except ImportError:
pass
if __name__ == "__main__":
# MIB module
try:
import snimpy.mib
ext_modules = [ snimpy.mib.ffi.verifier.get_extension() ]
except ImportError:
ext_modules = []
readme = open('README.rst').read()
history = open('HISTORY.rst').read().replace('.. :changelog:', '')
setup(name="snimpy",
version=snimpy.__version__,
classifiers = [
'Development Status :: 4 - Beta',
'Environment :: Console',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: ISC License (ISCL)',
'Operating System :: POSIX',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Topic :: System :: Networking',
'Topic :: Utilities',
'Topic :: System :: Monitoring'
],
url='https://github.com/vincentbernat/snimpy',
description=snimpy.__doc__,
long_description=readme + '\n\n' + history,
author=snimpy.__author__,
author_email=snimpy.__email__,
packages=["snimpy"],
entry_points = {
'console_scripts': [
'snimpy = snimpy.main:interact',
],
},
data_files = [('share/man/man1', ['man/snimpy.1'])],
ext_modules = ext_modules,
zip_safe = False,
install_requires = [ "cffi", "pysnmp >= 4" ],
setup_requires = [ "cffi" ],
tests_require = [ "cffi", "pysnmp >= 4", "nose", "mock" ],
test_suite="nose.collector"
)
snimpy-0.8.1/snimpy.egg-info/ 0000755 0001750 0001750 00000000000 12232211722 016612 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/snimpy.egg-info/top_level.txt 0000644 0001750 0001750 00000000041 12232211721 021336 0 ustar bernat bernat 0000000 0000000 _cffi__x679e504cxa0dd8598
snimpy
snimpy-0.8.1/snimpy.egg-info/not-zip-safe 0000644 0001750 0001750 00000000001 12232211721 021037 0 ustar bernat bernat 0000000 0000000
snimpy-0.8.1/snimpy.egg-info/dependency_links.txt 0000644 0001750 0001750 00000000001 12232211721 022657 0 ustar bernat bernat 0000000 0000000
snimpy-0.8.1/snimpy.egg-info/requires.txt 0000644 0001750 0001750 00000000020 12232211721 021201 0 ustar bernat bernat 0000000 0000000 cffi
pysnmp >= 4 snimpy-0.8.1/snimpy.egg-info/SOURCES.txt 0000644 0001750 0001750 00000002542 12232211721 020500 0 ustar bernat bernat 0000000 0000000 AUTHORS.rst
CONTRIBUTING.rst
HISTORY.rst
MANIFEST.in
README.rst
setup.py
docs/api.rst
docs/conf.py
docs/contributing.rst
docs/history.rst
docs/index.rst
docs/installation.rst
docs/license.rst
docs/usage.rst
docs/_static/snimpy.svg
docs/_themes/.gitignore
docs/_themes/LICENSE
docs/_themes/README
docs/_themes/flask_theme_support.py
docs/_themes/flask/layout.html
docs/_themes/flask/relations.html
docs/_themes/flask/theme.conf
docs/_themes/flask/static/flasky.css_t
docs/_themes/flask_small/layout.html
docs/_themes/flask_small/theme.conf
docs/_themes/flask_small/static/flasky.css_t
examples/add-vlan.py
examples/disable-port-ingress-filtering.py
examples/enable-lldp.py
examples/get-serial.py
examples/list-interfaces.py
examples/list-routes.py
examples/rename-vlan.py
examples/set-syslog-ntp.py
examples/vlan-and-interfaces.py
man/snimpy.1
snimpy/__init__.py
snimpy/__main__.py
snimpy/basictypes.py
snimpy/cffi_fix.py
snimpy/config.py
snimpy/main.py
snimpy/manager.py
snimpy/mib.py
snimpy/snmp.py
snimpy.egg-info/PKG-INFO
snimpy.egg-info/SOURCES.txt
snimpy.egg-info/dependency_links.txt
snimpy.egg-info/entry_points.txt
snimpy.egg-info/not-zip-safe
snimpy.egg-info/requires.txt
snimpy.egg-info/top_level.txt
tests/SNIMPY-INVALID-MIB.mib
tests/SNIMPY-MIB.mib
tests/agent.py
tests/test_basictypes.py
tests/test_main.py
tests/test_manager.py
tests/test_mib.py
tests/test_snmp.py snimpy-0.8.1/snimpy.egg-info/PKG-INFO 0000644 0001750 0001750 00000012157 12232211721 017714 0 ustar bernat bernat 0000000 0000000 Metadata-Version: 1.1
Name: snimpy
Version: 0.8.1
Summary: interactive SNMP tool
Home-page: https://github.com/vincentbernat/snimpy
Author: Vincent Bernat
Author-email: bernat@luffy.cx
License: UNKNOWN
Description: ===============================
snimpy
===============================
.. image:: https://badge.fury.io/py/snimpy.png
:target: http://badge.fury.io/py/snimpy
.. image:: https://travis-ci.org/vincentbernat/snimpy.png?branch=master
:target: https://travis-ci.org/vincentbernat/snimpy
.. image:: https://pypip.in/d/snimpy/badge.png
:target: https://crate.io/packages/snimpy?version=latest
Interactive SNMP tool.
*Snimpy* is a Python-based tool providing a simple interface to build
SNMP query. Here is a very simplistic example that allows us to
display the routing table of a given host::
load("IP-FORWARD-MIB")
m=M("localhost", "public", 2)
routes = m.ipCidrRouteNextHop
for x in routes:
net, netmask, tos, src = x
print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src))
You can either use *Snimpy* interactively throught its console
(derived from Python own console or from IPython_ if available) or
write *Snimpy* scripts which are just Python scripts with some global
variables available.
.. _IPython: http://ipython.org
* Free software: ISC license
* Documentation: http://snimpy.rtfd.org.
Features
--------
*Snimpy* is aimed at being the more Pythonic possible. You should forget
that you are doing SNMP requests. *Snimpy* will rely on MIB to hide SNMP
details. Here are some "features":
* MIB parser based on libsmi (through CFFI)
* SNMP requests are handled by PySNMP (SNMPv1, SNMPv2 and SNMPv3
support)
* scalars are just attributes of your session object
* columns are like a Python dictionary and made available as an
attribute
* getting an attribute is like issuing a GET method
* setting an attribute is like issuing a SET method
* iterating over a table is like using GETNEXT
* when something goes wrong, you get an exception
History
-------
0.8.0 (2013-10-25)
++++++++++++++++++
* Workaround a problem with CFFI extension installation.
0.8.0 (2013-10-19)
++++++++++++++++++++
* Python 3.3 support. Pypy support.
* PEP8 compliant.
* Sphinx documentation.
* Octet strings with a display hint are now treated differently than
plain octet strings (unicode). Notably, they can now be set using
the displayed format (for example, for MAC addresses).
0.7.0 (2013-09-23)
++++++++++++++++++
* Major rewrite.
* SNMP support is now provided through PySNMP_.
* MIB parsing is still done with libsmi_ but through CFFI instead of a
C module.
* More unittests. Many bugfixes.
.. _PySNMP: http://pysnmp.sourceforge.net/
.. _libsmi: http://www.ibr.cs.tu-bs.de/projects/libsmi/
0.6.4 (2013-03-21)
++++++++++++++++++
* GETBULK support.
* MacAddress SMI type support.
0.6.3 (2012-04-13)
++++++++++++++++++
* Support for IPython 0.12.
* Minor bugfixes.
0.6.2 (2012-01-19)
++++++++++++++++++
* Ability to return None instead of getting an exception.
0.6.1 (2012-01-14)
++++++++++++++++++
* Thread safety and efficiency.
0.6 (2012-01-10)
++++++++++++++++++
* SNMPv3 support
0.5.1 (2011-08-07)
++++++++++++++++++
* Compatibility with IPython 0.11.
* Custom timeouts and retries.
0.5 (2010-02-03)
++++++++++++++++++
* Check conformity of loaded modules.
* Many bugfixes.
0.4 (2009-06-06)
++++++++++++++++++
* Allow to cache requests.
0.3 (2008-11-23)
++++++++++++++++++
* Provide a manual page.
* Use a context manager to group SET requests.
0.2.1 (2008-09-28)
++++++++++++++++++
* First release on PyPI.
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: ISC License (ISCL)
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Networking
Classifier: Topic :: Utilities
Classifier: Topic :: System :: Monitoring
snimpy-0.8.1/snimpy.egg-info/entry_points.txt 0000644 0001750 0001750 00000000061 12232211721 022104 0 ustar bernat bernat 0000000 0000000 [console_scripts]
snimpy = snimpy.main:interact
snimpy-0.8.1/examples/ 0000755 0001750 0001750 00000000000 12232211722 015417 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/examples/disable-port-ingress-filtering.py 0000644 0001750 0001750 00000001072 12224343036 024014 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/snimpy
"""Disable port ingress filtering on Nortel switch (also known as
filter-unregistered-frames)."""
from __future__ import print_function
import sys
load("SNMPv2-MIB")
load("Q-BRIDGE-MIB")
s = M(host=sys.argv[1], community=sys.argv[2])
if "Ethernet Routing Switch 55" not in s.sysDescr:
print("Not a 5510")
sys.exit(1)
for id in s.dot1qPortIngressFiltering:
if s.dot1qPortIngressFiltering[id]:
print("Filtering on port %d of %s is not disabled, disable it." % (id, sys.argv[1]))
s.dot1qPortIngressFiltering[id] = False
snimpy-0.8.1/examples/set-syslog-ntp.py 0000755 0001750 0001750 00000004247 12224343036 020720 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/snimpy
"""
Set NTP or syslog server on various switches
Usage:
./set-syslog-ntp.py [syslog|ntp] host community first [...]
"""
from __future__ import print_function
import os
import sys
load("SNMPv2-MIB")
host = sys.argv[2]
targets = sys.argv[4:]
operation = sys.argv[1]
try:
s = M(host=host, community=sys.argv[3])
sid = str(s.sysObjectID)
except snmp.SNMPException, e:
print("%s: %s" % (host, e))
sys.exit(1)
if sid.startswith("1.3.6.1.4.1.45.3."):
# Nortel
print("%s is Nortel 55xx" % host)
load(os.path.expanduser("~/.snmp/mibs/SYNOPTICS-ROOT-MIB"))
load(os.path.expanduser("~/.snmp/mibs/S5-ROOT-MIB"))
if operation == "ntp":
load(os.path.expanduser("~/.snmp/mibs/S5-AGENT-MIB"))
s.s5AgSntpPrimaryServerAddress = targets[0]
if len(targets) > 1:
s.s5AgSntpSecondaryServerAddress = targets[1]
else:
s.s5AgSntpSecondaryServerAddress = "0.0.0.0"
s.s5AgSntpState = "unicast"
s.s5AgSntpManualSyncRequest = "requestSync"
elif operation == "syslog":
load(os.path.expanduser("~/.snmp/mibs/BN-LOG-MESSAGE-MIB"))
s.bnLogMsgRemoteSyslogAddress = targets[0]
s.bnLogMsgRemoteSyslogSaveTargets = "msgTypeInformational"
s.bnLogMsgRemoteSyslogEnabled = True
elif sid.startswith("1.3.6.1.4.1.1872."):
print("%s is Alteon" % host)
load(os.path.expanduser("~/.snmp/mibs/ALTEON-ROOT-MIB"))
if operation == "ntp":
s.agNewCfgNTPServer = targets[0]
if len(targets) > 1:
s.agNewCfgNTPSecServer = targets[1]
else:
s.agNewCfgNTPSecServer = "0.0.0.0"
s.agNewCfgNTPService = "enabled"
elif operation == "syslog":
s.agNewCfgSyslogHost = targets[0]
s.agNewCfgSyslogFac = "local2"
if len(targets) > 1:
s.agNewCfgSyslog2Host = targets[1]
s.agNewCfgSyslog2Fac = "local2"
else:
s.agNewCfgSyslog2Host = "0.0.0.0"
if s.agApplyPending == "applyNeeded":
if s.agApplyConfig == "complete":
s.agApplyConfig = "idle"
s.agApplyConfig = "apply"
else:
print("%s is unknown (%s)" % (host, s.sysDescr))
sys.exit(1)
snimpy-0.8.1/examples/vlan-and-interfaces.py 0000644 0001750 0001750 00000001162 11730211606 021615 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/snimpy
"""
On Nortel switches, list vlan on all active ports
"""
import os
import sys
load("SNMPv2-MIB")
load("IF-MIB")
load(os.path.expanduser("~/.snmp/mibs/RAPID-CITY-MIB"))
load(os.path.expanduser("~/.snmp/mibs/RC-VLAN-MIB"))
s = M(host=sys.argv[1], community=sys.argv[2])
vlans = {}
for interface in s.ifIndex:
if s.ifOperStatus[interface] == "up":
vlans[int(interface)] = []
for vlan in s.rcVlanId:
for interface in vlans:
if s.rcVlanStaticMembers[vlan] & interface:
vlans[interface].append("%s(%s)" % (vlan, s.rcVlanName[vlan]))
import pprint
pprint.pprint(vlans)
snimpy-0.8.1/examples/list-routes.py 0000644 0001750 0001750 00000001212 12224343036 020264 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/snimpy
from __future__ import print_function
from socket import inet_ntoa
load("IP-FORWARD-MIB")
m=M()
print("Using IP-FORWARD-MIB::ipCidrRouteTable...")
routes = m.ipCidrRouteNextHop
for x in routes:
net, netmask, tos, src = x
print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src))
print
print("Using IP-FORWARD-MIB::inetCidrRouteTable...")
routes = m.inetCidrRouteIfIndex
for x in routes:
dsttype, dst, prefix, oid, nhtype, nh = x
if dsttype != "ipv4" or nhtype != "ipv4":
print("Non-IPv4 route")
continue
print("%15s/%-2d via %-15s" % (inet_ntoa(dst), prefix, inet_ntoa(nh)))
snimpy-0.8.1/examples/enable-lldp.py 0000644 0001750 0001750 00000003430 12224343036 020155 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/snimpy
"""Enable LLDP.
Generic procedure but we restrict ourself to Nortel 55x0.
"""
from __future__ import print_function
import sys
import os
load("SNMPv2-MIB")
for l in ["LLDP", "LLDP-EXT-DOT3", "LLDP-EXT-DOT1"]:
load(os.path.expanduser("~/.snmp/mibs/%s-MIB" % l))
s = M(host=sys.argv[1], community=sys.argv[2])
try:
type = s.sysDescr
except snmp.SNMPException:
print("Cannot process %s: bad community?" % sys.argv[1])
sys.exit(1)
if not type.startswith("Ethernet Routing Switch 55") and \
not type.startswith("Ethernet Switch 425"):
print("Not a 55x0: %s" % type)
sys.exit(1)
print("Processing %s..." % sys.argv[1])
try:
for oid in s.lldpConfigManAddrPortsTxEnable:
if oid[0] == "ipV4":
s.lldpConfigManAddrPortsTxEnable[oid] = "\xff"*10
except snmp.SNMPNoSuchObject:
print("No LLDP for this switch")
sys.exit(2)
dot3 = True
for port in s.lldpPortConfigAdminStatus:
s.lldpPortConfigAdminStatus[port] = "txAndRx"
s.lldpPortConfigTLVsTxEnable[port] = ["portDesc",
"sysName",
"sysDesc",
"sysCap" ]
# Dot3
try:
if dot3:
s.lldpXdot3PortConfigTLVsTxEnable[port] = ["macPhyConfigStatus",
"powerViaMDI",
"linkAggregation",
"maxFrameSize"]
except snmp.SNMPException:
print("No Dot3")
dot3 = False
# Dot1
try:
for port,vlan in s.lldpXdot1ConfigVlanNameTxEnable:
s.lldpXdot1ConfigVlanNameTxEnable[port, vlan] = True
except snmp.SNMPException:
print("No Dot1")
print("Success!")
snimpy-0.8.1/examples/list-interfaces.py 0000644 0001750 0001750 00000000231 12224343036 021066 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/snimpy
from __future__ import print_function
load("IF-MIB")
m=M()
for i in m.ifDescr:
print("Interface %3d: %s" % (i, m.ifDescr[i]))
snimpy-0.8.1/examples/get-serial.py 0000644 0001750 0001750 00000001630 12224343036 020032 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/snimpy
"""
Get serial number of a given equipment using ENTITY-MIB
"""
from __future__ import print_function
import sys
load("ENTITY-MIB")
host=sys.argv[1]
s = M(host=host, community=sys.argv[2])
# Locate parent of all other elements
print("[-] %s: Search for parent element" % host)
parent = None
for i in s.entPhysicalContainedIn:
if s.entPhysicalContainedIn[i] == 0:
parent = i
break
if parent is None:
print("[!] %s: Unable to find parent" % host)
sys.exit(1)
print("[+] %s: %s" % (host, s.entPhysicalDescr[parent]))
print("[+] %s: HW %s, FW %s, SW %s" % (host,
s.entPhysicalHardwareRev[parent],
s.entPhysicalFirmwareRev[parent],
s.entPhysicalSoftwareRev[parent]))
print("[+] %s: SN %s" % (host,
s.entPhysicalSerialNum[parent]))
snimpy-0.8.1/examples/add-vlan.py 0000644 0001750 0001750 00000002363 12224343036 017470 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/snimpy
"""
On Nortel switches, create a new VLAN and tag it on "TagAll" ports.
"""
from __future__ import print_function
import os
import sys
load("SNMPv2-MIB")
load(os.path.expanduser("~/.snmp/mibs/RAPID-CITY-MIB"))
load(os.path.expanduser("~/.snmp/mibs/RC-VLAN-MIB"))
vlanNumber = int(sys.argv[3])
vlanName = sys.argv[4]
s = M(host=sys.argv[1], community=sys.argv[2])
# Create the VLAN
if vlanNumber not in s.rcVlanId:
print("VLAN %d will be created with name %s on %s" % (vlanNumber, vlanName, sys.argv[1]))
with s:
s.rcVlanRowStatus[vlanNumber] = "createAndGo"
s.rcVlanName[vlanNumber] = vlanName
s.rcVlanType[vlanNumber] = "byPort"
else:
print("VLAN %d already exists on %s" % (vlanNumber, sys.argv[1]))
# Just set the name
if s.rcVlanName[vlanNumber] != vlanName:
s.rcVlanName[vlanNumber] = vlanName
# Which ports are tagall ?
tagged = [port for port in s.rcVlanPortPerformTagging
if s.rcVlanPortPerformTagging[port] ]
if len(tagged) != 2 and len(tagged) != 3:
print("%s does not have exactly two or three tagged ports (%r)" % (sys.argv[1], tagged))
sys.exit(1)
print("VLAN %d will be tagged on ports %s" % (vlanNumber, tagged))
s.rcVlanStaticMembers[vlanNumber] |= tagged
snimpy-0.8.1/examples/rename-vlan.py 0000644 0001750 0001750 00000001160 12224343036 020201 0 ustar bernat bernat 0000000 0000000 #!/usr/bin/snimpy
from __future__ import print_function
import os
import sys
load("SNMPv2-MIB")
load(os.path.expanduser("~/.snmp/mibs/RAPID-CITY-MIB"))
load(os.path.expanduser("~/.snmp/mibs/RC-VLAN-MIB"))
vlanNumber = int(sys.argv[3])
newName = sys.argv[4]
s = M(host=sys.argv[1], community=sys.argv[2])
try:
cur = s.rcVlanName[vlanNumber]
except snmp.SNMPException:
print("%s is not a Nortel switch or does not have VLAN %d" % (sys.argv[1], vlanNumber))
sys.exit(1)
if cur != newName:
s.rcVlanName[vlanNumber] = newName
print("Setting VLAN %d of %s as %s: done." % (vlanNumber, sys.argv[1], newName))
snimpy-0.8.1/PKG-INFO 0000644 0001750 0001750 00000012157 12232211722 014704 0 ustar bernat bernat 0000000 0000000 Metadata-Version: 1.1
Name: snimpy
Version: 0.8.1
Summary: interactive SNMP tool
Home-page: https://github.com/vincentbernat/snimpy
Author: Vincent Bernat
Author-email: bernat@luffy.cx
License: UNKNOWN
Description: ===============================
snimpy
===============================
.. image:: https://badge.fury.io/py/snimpy.png
:target: http://badge.fury.io/py/snimpy
.. image:: https://travis-ci.org/vincentbernat/snimpy.png?branch=master
:target: https://travis-ci.org/vincentbernat/snimpy
.. image:: https://pypip.in/d/snimpy/badge.png
:target: https://crate.io/packages/snimpy?version=latest
Interactive SNMP tool.
*Snimpy* is a Python-based tool providing a simple interface to build
SNMP query. Here is a very simplistic example that allows us to
display the routing table of a given host::
load("IP-FORWARD-MIB")
m=M("localhost", "public", 2)
routes = m.ipCidrRouteNextHop
for x in routes:
net, netmask, tos, src = x
print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src))
You can either use *Snimpy* interactively throught its console
(derived from Python own console or from IPython_ if available) or
write *Snimpy* scripts which are just Python scripts with some global
variables available.
.. _IPython: http://ipython.org
* Free software: ISC license
* Documentation: http://snimpy.rtfd.org.
Features
--------
*Snimpy* is aimed at being the more Pythonic possible. You should forget
that you are doing SNMP requests. *Snimpy* will rely on MIB to hide SNMP
details. Here are some "features":
* MIB parser based on libsmi (through CFFI)
* SNMP requests are handled by PySNMP (SNMPv1, SNMPv2 and SNMPv3
support)
* scalars are just attributes of your session object
* columns are like a Python dictionary and made available as an
attribute
* getting an attribute is like issuing a GET method
* setting an attribute is like issuing a SET method
* iterating over a table is like using GETNEXT
* when something goes wrong, you get an exception
History
-------
0.8.0 (2013-10-25)
++++++++++++++++++
* Workaround a problem with CFFI extension installation.
0.8.0 (2013-10-19)
++++++++++++++++++++
* Python 3.3 support. Pypy support.
* PEP8 compliant.
* Sphinx documentation.
* Octet strings with a display hint are now treated differently than
plain octet strings (unicode). Notably, they can now be set using
the displayed format (for example, for MAC addresses).
0.7.0 (2013-09-23)
++++++++++++++++++
* Major rewrite.
* SNMP support is now provided through PySNMP_.
* MIB parsing is still done with libsmi_ but through CFFI instead of a
C module.
* More unittests. Many bugfixes.
.. _PySNMP: http://pysnmp.sourceforge.net/
.. _libsmi: http://www.ibr.cs.tu-bs.de/projects/libsmi/
0.6.4 (2013-03-21)
++++++++++++++++++
* GETBULK support.
* MacAddress SMI type support.
0.6.3 (2012-04-13)
++++++++++++++++++
* Support for IPython 0.12.
* Minor bugfixes.
0.6.2 (2012-01-19)
++++++++++++++++++
* Ability to return None instead of getting an exception.
0.6.1 (2012-01-14)
++++++++++++++++++
* Thread safety and efficiency.
0.6 (2012-01-10)
++++++++++++++++++
* SNMPv3 support
0.5.1 (2011-08-07)
++++++++++++++++++
* Compatibility with IPython 0.11.
* Custom timeouts and retries.
0.5 (2010-02-03)
++++++++++++++++++
* Check conformity of loaded modules.
* Many bugfixes.
0.4 (2009-06-06)
++++++++++++++++++
* Allow to cache requests.
0.3 (2008-11-23)
++++++++++++++++++
* Provide a manual page.
* Use a context manager to group SET requests.
0.2.1 (2008-09-28)
++++++++++++++++++
* First release on PyPI.
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: ISC License (ISCL)
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Networking
Classifier: Topic :: Utilities
Classifier: Topic :: System :: Monitoring
snimpy-0.8.1/AUTHORS.rst 0000644 0001750 0001750 00000000204 12230342273 015460 0 ustar bernat bernat 0000000 0000000 Development Lead
----------------
* Vincent Bernat
Contributors
------------
* Jakub Wroniecki
* Julian Taylor
snimpy-0.8.1/tests/ 0000755 0001750 0001750 00000000000 12232211722 014743 5 ustar bernat bernat 0000000 0000000 snimpy-0.8.1/tests/test_main.py 0000644 0001750 0001750 00000001733 12225620542 017312 0 ustar bernat bernat 0000000 0000000 import unittest
import os
import tempfile
import code # nopep8
import mock
from snimpy.main import interact
from multiprocessing import Process
import agent
class TestMain(unittest.TestCase):
"""Test the main shell"""
@classmethod
def setUpClass(cls):
cls.agent = agent.TestAgent()
@classmethod
def tearDownClass(cls):
cls.agent.terminate()
def test_loadfile(self):
script = tempfile.NamedTemporaryFile(delete=False)
try:
script.write("""
load("IF-MIB")
m = M(host="127.0.0.1:{0}",
community="public",
version=2)
assert(m.ifDescr[1] == "lo")
""".format(self.agent.port).encode("ascii"))
script.close()
with mock.patch("code.InteractiveInterpreter.write"):
p = Process(target=interact, args=((script.name,),))
p.start()
p.join()
self.assertEqual(p.exitcode, 0)
finally:
os.unlink(script.name)
snimpy-0.8.1/tests/test_basictypes.py 0000644 0001750 0001750 00000057553 12230342274 020546 0 ustar bernat bernat 0000000 0000000 import unittest
import os
import re
import socket
import mock
from datetime import timedelta
from snimpy import mib, basictypes
from pysnmp.proto import rfc1902
class TestBasicTypes(unittest.TestCase):
def setUp(self):
mib.load(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"SNIMPY-MIB.mib"))
def tearDown(self):
mib.reset()
def testInteger(self):
"""Test integer basic type"""
a = basictypes.build("SNIMPY-MIB", "snimpyInteger", 18)
self.assert_(isinstance(a, basictypes.Integer))
self.assertEqual(a, 18)
self.assertEqual(a + 10, 28)
a = basictypes.build("SNIMPY-MIB", "snimpyInteger", 4)
self.assertEqual(a, 4)
self.assertEqual(a * 4, 16)
a = basictypes.build("SNIMPY-MIB", "snimpyInteger", 5)
self.assertEqual(a, 5)
self.assert_(a < 6)
# self.assert_(a > 4.6) # type coercion does not work
self.assert_(a > 4)
self.assertRaises(TypeError,
basictypes.build, ("SNIMPY-MIB",
"snimpyInteger", [1, 2, 3]))
def testString(self):
"""Test string basic type"""
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"hello")
self.assert_(isinstance(a, basictypes.String))
self.assertEqual(a, "hello")
self.assertEqual(a + " john", "hello john")
self.assertEqual(a * 2, "hellohello")
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"hello john")
self.assert_("john" in a)
self.assert_("steve" not in a)
self.assertEqual(a[1], 'e')
self.assertEqual(a[1:4], 'ell')
self.assertEqual(len(a), 10)
def testStringFromBytes(self):
"""Test string basic type when built from bytes"""
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"hello")
self.assert_(isinstance(a, basictypes.String))
self.assertEqual(a, "hello")
self.assertEqual(a + " john", "hello john")
self.assertEqual(a * 2, "hellohello")
def testStringEncoding(self):
"""Test we can create an UTF-8 encoded string"""
a = basictypes.build("SNIMPY-MIB", "snimpyString", u"hello")
self.assertEqual(a, u"hello")
self.assertEqual(a, "hello")
a = basictypes.build(
"SNIMPY-MIB",
"snimpyUnicodeString",
u"\U0001F60E Hello")
self.assertEqual(a, u"\U0001F60E Hello")
a = basictypes.build(
"SNIMPY-MIB",
"snimpyUnicodeString",
b'\xf0\x9f\x98\x8e Hello')
self.assertEqual(a, u"\U0001F60E Hello")
self.assertRaises(UnicodeError,
basictypes.build, "SNIMPY-MIB", "snimpyString",
b'\xf0\x9f\x98\x8e Hello')
def testOctetString(self):
"""Test octet string basic type"""
a = basictypes.build("SNIMPY-MIB", "snimpyOctetString", b"hello\x41")
self.assert_(isinstance(a, basictypes.OctetString))
self.assertEqual(a, b"hello\x41")
self.assertEqual(len(a), 6)
def testIpAddress(self):
"""Test IP address basic type"""
a = basictypes.build(
"SNIMPY-MIB",
"snimpyIpAddress",
socket.inet_aton("10.0.4.5"))
self.assert_(isinstance(a, basictypes.IpAddress))
self.assertEqual(a, "10.0.4.5")
self.assertEqual(a, "10.00.4.05")
self.assertEqual(a, [10, 0, 4, 5])
self.assertEqual(a[2], 4)
self.assert_(a < "10.1.2.4")
self.assert_(a > "10.0.0.1")
a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", [1, 2, 3, 5])
self.assertEqual(a, "1.2.3.5")
a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", "10.0.4.5")
self.assertEqual(a, "10.0.4.5")
self.assertEqual(a, [10, 0, 4, 5])
a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", b"1001")
self.assertEqual(a, [49, 48, 48, 49])
a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", b"0101")
self.assertEqual(a, [48, 49, 48, 49])
a = basictypes.build("SNIMPY-MIB", "snimpyIpAddress", "100")
self.assertEqual(a, [0, 0, 0, 100])
def testIncorrectIpAddress(self):
"""Test inappropriate IP addresses"""
self.assertRaises(ValueError,
basictypes.build,
"SNIMPY-MIB", "snimpyIpAddress", "999.5.6.4")
self.assertRaises(ValueError,
basictypes.build,
"SNIMPY-MIB", "snimpyIpAddress", "AAA")
self.assertRaises(ValueError,
basictypes.build,
"SNIMPY-MIB", "snimpyIpAddress", "AAACC")
def testEnum(self):
"""Test enum basic type"""
a = basictypes.build("SNIMPY-MIB", "snimpyEnum", 1)
self.assert_(isinstance(a, basictypes.Enum))
self.assertEqual(a, 1)
self.assertEqual(a, "up")
a = basictypes.build("SNIMPY-MIB", "snimpyEnum", "down")
self.assertEqual(a, "down")
self.assert_(a != "up")
self.assertEqual(a, 2)
self.assertEqual(str(a), "down(2)")
self.assertRaises(ValueError,
basictypes.build,
"SNIMPY-MIB", "snimpyEnum", "unknown")
self.assertEqual(str(a), "down(2)")
a = basictypes.build("SNIMPY-MIB", "snimpyEnum", 54)
self.assertEqual(a, 54)
def testOid(self):
"""Test OID basic type"""
a = basictypes.build("SNIMPY-MIB", "snimpyObjectId",
mib.get("SNIMPY-MIB", "snimpyInteger"))
self.assert_(isinstance(a, basictypes.Oid))
self.assertEqual(a, mib.get("SNIMPY-MIB", "snimpyInteger"))
self.assertEqual(a, mib.get("SNIMPY-MIB", "snimpyInteger").oid)
# Suboid
self.assert_((list(mib.get("SNIMPY-MIB",
"snimpyInteger").oid) + [2, 3]) in a)
self.assert_((list(mib.get("SNIMPY-MIB",
"snimpyInteger").oid)[:-1] +
[29, 3]) not in a)
# Also accepts list
a = basictypes.build("SNIMPY-MIB", "snimpyObjectId",
(1, 2, 3, 4))
self.assertEqual(a, (1, 2, 3, 4))
self.assert_((1, 2, 3, 4, 5) in a)
self.assert_((3, 4, 5, 6) not in a)
def testBoolean(self):
"""Test boolean basic type"""
a = basictypes.build("SNIMPY-MIB", "snimpyBoolean", True)
self.assert_(isinstance(a, basictypes.Boolean))
self.assertEqual(a, True)
self.assert_(a)
self.assert_(not(not(a)))
self.assertEqual(not(a), False)
a = basictypes.build("SNIMPY-MIB", "snimpyBoolean", "false")
self.assertEqual(a, False)
b = basictypes.build("SNIMPY-MIB", "snimpyBoolean", True)
self.assertEqual(a or b, True)
self.assertEqual(a and b, False)
def testTimeticks(self):
"""Test timeticks basic type"""
a = basictypes.build("SNIMPY-MIB", "snimpyTimeticks", 676544)
self.assert_(isinstance(a, basictypes.Timeticks))
# We can compare to int but otherwise, this is a timedelta
self.assertEqual(a, 676544)
self.assertEqual(str(a), '1:52:45.440000')
self.assertEqual(a, timedelta(0, 6765, 440000))
a = basictypes.build("SNIMPY-MIB", "snimpyTimeticks",
timedelta(1, 3))
self.assertEqual(str(a), '1 day, 0:00:03')
self.assertEqual(a, (3 + 3600 * 24) * 100)
self.assert_(a != (3 + 3600 * 24) * 100 + 1)
self.assert_(a < timedelta(1, 4))
self.assert_(a > timedelta(1, 1))
self.assert_(a > 654)
self.assert_(a >= 654)
self.assert_(a < (3 + 3600 * 24) * 100 + 2)
self.assertEqual(a,
basictypes.build("SNIMPY-MIB", "snimpyTimeticks",
timedelta(1, 3)))
self.assert_(a < basictypes.build("SNIMPY-MIB", "snimpyTimeticks",
timedelta(100, 30)))
def testBits(self):
"""Test bit basic type"""
a = basictypes.build("SNIMPY-MIB", "snimpyBits", [1, 2])
self.assert_(isinstance(a, basictypes.Bits))
self.assertEqual(a, [2, 1])
self.assertEqual(a, (1, 2))
self.assertEqual(a, set([1, 2]))
self.assertEqual(a, ["second", "third"])
self.assertEqual(a, set(["second", "third"]))
self.assertEqual(a, ["second", 2])
self.assert_(a != ["second"])
self.assertFalse(a == ["second"])
self.assertFalse(a != ["second", 2])
a |= "last"
a |= ["last", "second"]
self.assertEqual(a, ["second", "last", "third"])
self.assertEqual(str(a), "second(1), third(2), last(7)")
a -= 1
a -= 1
self.assertEqual(a, ["last", "third"])
self.assertEqual(a & "last", True)
self.assertEqual(a & "second", False)
self.assertEqual(a & ["last", 2], True)
self.assertEqual(a & set(["last", 2]), True)
self.assertEqual(a & ["last", 0], True)
self.assertEqual(a & ["second", 0], False)
a = basictypes.build("SNIMPY-MIB", "snimpyBits",
set(["first", "second"]))
self.assertEqual(a, [0, 1])
a = basictypes.build("SNIMPY-MIB", "snimpyBits", [])
self.assertEqual(a, [])
self.assertEqual(str(a), "")
def testInexistentBits(self):
"""Check we cannot set inexistent bits"""
a = basictypes.build("SNIMPY-MIB", "snimpyBits", [1, 2])
self.assert_(a & 1)
def nope(a):
a |= 3
self.assertRaises(ValueError, nope, a)
def testStringAsBits(self):
"""Test using bit specific operator with string"""
a = basictypes.build(
"SNIMPY-MIB",
"snimpyOctetString",
b"\x17\x00\x01")
self.assert_(isinstance(a, basictypes.OctetString))
b = [7, 6, 5, 3, 23]
for i in range(30):
if i in b:
self.assert_(a & i)
else:
self.assert_(not(a & i))
self.assert_(a & [5, 7])
self.assert_(not(a & [5, 9]))
a |= [2, 10]
a -= 22
a -= [23, 22]
self.assert_(a & [2, 10])
self.assert_(not(a & 23))
self.assertEqual(a, b"\x37\x20\x00")
a |= 31
self.assertEqual(a, b"\x37\x20\x00\x01")
def testPacking(self):
"""Test pack() function"""
self.assertEqual(basictypes.build("SNIMPY-MIB",
"snimpyString",
"Hello world").pack(),
rfc1902.OctetString("Hello world"))
self.assertEqual(basictypes.build("SNIMPY-MIB",
"snimpyInteger",
18).pack(),
rfc1902.Integer(18))
self.assertEqual(basictypes.build("SNIMPY-MIB",
"snimpyInteger",
1804).pack(),
rfc1902.Integer(1804))
self.assertEqual(basictypes.build("SNIMPY-MIB",
"snimpyEnum",
"testing").pack(),
rfc1902.Integer(3))
self.assertEqual(basictypes.build("SNIMPY-MIB",
"snimpyIpAddress",
"10.11.12.13").pack(),
rfc1902.IpAddress("10.11.12.13"))
self.assertEqual(basictypes.build("SNIMPY-MIB",
"snimpyObjectId",
(1, 2, 3, 4)).pack(),
rfc1902.univ.ObjectIdentifier((1, 2, 3, 4)))
self.assertEqual(basictypes.build("SNIMPY-MIB",
"snimpyTimeticks",
timedelta(3, 2)).pack(),
rfc1902.TimeTicks(3 * 3600 * 24 * 100 + 2 * 100))
self.assertEqual(basictypes.build("SNIMPY-MIB",
"snimpyBits",
[1, 7]).pack(),
rfc1902.Bits(b"\x41"))
def testOidConversion(self):
"""Test conversion to/from OID."""
tt = {("snimpySimpleIndex", 47): (47,),
("snimpyComplexFirstIP", "10.14.15.4"): (10, 14, 15, 4),
("snimpyComplexSecondIP", (14, 15, 16, 17)): (14, 15, 16, 17),
("snimpyIndexOidVarLen", (47, 48, 49)): (3, 47, 48, 49),
("snimpyIndexVarLen", "hello1"): tuple([len("hello1")] +
[ord(a)
for a in "hello1"]),
("snimpyIndexFixedLen", "hello2"): tuple(ord(a)
for a in "hello2"),
("snimpyIndexImplied", "hello3"): tuple(ord(a)
for a in "hello3"),
}
for t, v in tt:
oid = basictypes.build("SNIMPY-MIB",
t,
v).toOid()
self.assertEqual(oid,
tt[t, v])
# Test double conversion
self.assertEqual(mib.get("SNIMPY-MIB", t).type.fromOid(
mib.get("SNIMPY-MIB", t), oid),
(len(tt[t, v]), v))
def testOidGreedy(self):
"""Test greediness of fromOid."""
tt = {
"snimpyIndexVarLen":
((5, 104, 101, 108, 108, 111, 111, 111, 111), (6, "hello")),
"snimpyIndexFixedLen":
((104, 101, 108, 108, 111, 49, 49, 111), (6, "hello1")),
"snimpyIndexImplied":
((104, 101, 108, 108, 111, 50), (6, "hello2")),
"snimpyComplexFirstIP":
((15, 15, 16, 100, 23, 74, 87), (4, "15.15.16.100")),
"snimpySimpleIndex": ((17, 19, 20), (1, 17)),
"snimpyIndexOidVarLen": ((3, 247, 145, 475568, 475, 263),
(4, (247, 145, 475568))),
}
for t in tt:
self.assertEqual(mib.get("SNIMPY-MIB", t).type.fromOid(
mib.get("SNIMPY-MIB", t), tt[t][0]),
tt[t][1])
# Test if too short
tt = {"snimpyComplexFirstIP": (17, 19, 20),
"snimpyIndexFixedLen": (104, 101, 108),
"snimpyIndexVarLen": (6, 102, 103, 104, 105),
"snimpyIndexOidVarLen": (3, 247, 145),
}
for t in tt:
self.assertRaises(ValueError,
mib.get("SNIMPY-MIB", t).type.fromOid,
mib.get("SNIMPY-MIB", t), tt[t])
def testDisplay(self):
"""Test string transformation"""
self.assertEqual(str(basictypes.build("SNIMPY-MIB",
"snimpyInteger",
18)), "0.18")
self.assertEqual(str(basictypes.build("SNIMPY-MIB",
"snimpyInteger",
8)), "0.08")
self.assertEqual(str(basictypes.build("SNIMPY-MIB",
"snimpyInteger",
288)), "2.88")
self.assertEqual(str(basictypes.build("SNIMPY-MIB",
"snimpyInteger",
28801)), "288.01")
self.assertEqual(str(basictypes.build("SNIMPY-MIB",
"snimpyString",
"test")), "test")
self.assertEqual(str(basictypes.build("SNIMPY-MIB",
"snimpyOctetString",
b"test")), str(b"test"))
self.assertEqual(str(basictypes.build("SNIMPY-MIB",
"snimpyOctetString",
b"tes\x05")), str(b"tes\x05"))
def testDisplayFormat(self):
"""Test display some with some formats"""
with mock.patch("snimpy.mib.Node.fmt",
new_callable=mock.PropertyMock) as e:
e.return_value = "255a"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(str(a), "test")
e.return_value = "1x:"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(str(a), "74:65:73:74")
e.return_value = "2a:"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(str(a), "te:st")
e.return_value = "3a:"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(str(a), "tes:t")
e.return_value = "4a"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(str(a), "test")
e.return_value = "2o+1a"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(str(a), "072145+st")
e.return_value = "*2a:+255a"
a = basictypes.build("SNIMPY-MIB", "snimpyString",
b"\x03testtest...")
self.assertEqual(str(a), "te:st:te+st...")
e.return_value = "2a1x:"
a = basictypes.build("SNIMPY-MIB", "snimpyString",
b"aatest")
self.assertEqual(str(a), "aa74:65:73:74")
e.return_value = "*2a+1a:-*3a?="
a = basictypes.build("SNIMPY-MIB", "snimpyString",
b"\x04testtestZ\x02testes\x03testestes")
self.assertEqual(str(a), "te+st+te+st+Z-tes?tes=tes?tes?tes")
def testInputFormat(self):
"""Test we can input a string with a given format"""
with mock.patch("snimpy.mib.Node.fmt",
new_callable=mock.PropertyMock) as e:
e.return_value = "255a"
a = basictypes.build("SNIMPY-MIB", "snimpyString", u"test")
self.assertEqual(a.pack(), b"test")
e.return_value = "1x:"
a = basictypes.build("SNIMPY-MIB", "snimpyString", u"74:65:73:74")
self.assertEqual(a.pack(), b"test")
a = basictypes.build("SNIMPY-MIB", "snimpyString", u"74:6:73:4")
self.assertEqual(a.pack(), b"t\x06s\x04")
e.return_value = "2a:"
a = basictypes.build("SNIMPY-MIB", "snimpyString", u"te:st")
self.assertEqual(a.pack(), b"test")
e.return_value = "3a:"
a = basictypes.build("SNIMPY-MIB", "snimpyString", u"tes:t")
self.assertEqual(a.pack(), b"test")
e.return_value = "4a"
a = basictypes.build("SNIMPY-MIB", "snimpyString", u"test")
self.assertEqual(a.pack(), b"test")
e.return_value = "2o+1a"
a = basictypes.build("SNIMPY-MIB", "snimpyString", u"072145+st")
self.assertEqual(a.pack(), b"test")
e.return_value = "*2a:+255a"
a = basictypes.build(
"SNIMPY-MIB",
"snimpyString",
u"te:st:te+st...")
self.assertEqual(a.pack(), b"\x03testtest...")
e.return_value = "2a1x:"
a = basictypes.build("SNIMPY-MIB", "snimpyString",
u"aa74:65:73:74")
self.assertEqual(a.pack(), b"aatest")
e.return_value = "*2a+@1a:-*3a?="
a = basictypes.build("SNIMPY-MIB", "snimpyString",
u"te+st+te+st@Z-tes?tes=tes?tes?tes")
self.assertEqual(a.pack(), b"\x04testtestZ\x02testes\x03testestes")
def testRepr(self):
"""Test representation"""
self.assertEqual(repr(basictypes.build("SNIMPY-MIB",
"snimpyInteger",
18)), "")
self.assertEqual(repr(basictypes.build("SNIMPY-MIB",
"snimpyObjectId",
(1, 3, 6, 1, 4, 1,
45, 3, 52, 1))),
"")
self.assertEqual(repr(basictypes.build("SNIMPY-MIB",
"snimpyIpAddress",
"124.24.14.3")),
"")
self.assertEqual(repr(basictypes.build("SNIMPY-MIB",
"snimpyString",
"45754dfgf")),
"")
self.assertEqual(repr(basictypes.build("SNIMPY-MIB",
"snimpyEnum",
2)),
"")
self.assertEqual(repr(basictypes.build("SNIMPY-MIB",
"snimpyBoolean",
False)),
"")
self.assertEqual(repr(basictypes.build("SNIMPY-MIB",
"snimpyCounter",
4547)),
"")
self.assertEqual(repr(basictypes.build("SNIMPY-MIB",
"snimpyBits",
["first", "second"])),
"")
def testEqualityWithDisplay(self):
"""Test we can check for equality with displayed form"""
a = basictypes.build("SNIMPY-MIB", "snimpyString", "test")
self.assertEqual(a, "test")
with mock.patch("snimpy.mib.Node.fmt",
new_callable=mock.PropertyMock) as e:
e.return_value = "255a"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(a, "test")
e.return_value = "1x:"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(a, "74:65:73:74")
e.return_value = "2a:"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(a, "te:st")
e.return_value = "3a:"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(a, "tes:t")
e.return_value = "4a"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(a, "test")
e.return_value = "2o+1a"
a = basictypes.build("SNIMPY-MIB", "snimpyString", b"test")
self.assertEqual(a, "072145+st")
self.assertNotEqual(a, "072145+sta")
self.assertFalse(a != "072145+st")
e.return_value = "*2a:+255a"
a = basictypes.build("SNIMPY-MIB",
"snimpyString",
b"\x03testtest...")
self.assertEqual(a, "te:st:te+st...")
def testEqualityUnicode(self):
"""Test that equality works for both unicode and bytes"""
a = basictypes.build("SNIMPY-MIB", "snimpyString", "test")
self.assertEqual(a, "test")
a = basictypes.build("SNIMPY-MIB", "snimpyString", "test")
self.assertEqual(a, u"test")
def testLikeAString(self):
"""Test String is like str"""
a = basictypes.build("SNIMPY-MIB",
"snimpyString",
"4521dgf")
self.assert_(a.startswith("4521"))
self.assertEqual(a.upper(), "4521DGF")
self.assert_(re.match("[0-9]+[defg]+", a))
snimpy-0.8.1/tests/test_manager.py 0000644 0001750 0001750 00000026161 12225620542 020002 0 ustar bernat bernat 0000000 0000000 import unittest
import os
import time
from datetime import timedelta
from snimpy.manager import load, Manager, snmp
import agent
class TestManager(unittest.TestCase):
@classmethod
def setUpClass(cls):
load('IF-MIB')
load('SNMPv2-MIB')
load(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"SNIMPY-MIB.mib"))
cls.agent = agent.TestAgent()
@classmethod
def tearDownClass(cls):
cls.agent.terminate()
def setUp(self):
self.manager = Manager(host="127.0.0.1:{0}".format(self.agent.port),
community="public",
version=2)
self.session = self.manager._session
class TestManagerGet(TestManager):
"""Test getting stuff from manager"""
def testGetScalar(self):
"""Retrieve some simple scalar values"""
self.assertEqual(self.manager.sysDescr, "Snimpy Test Agent")
self.assertEqual(self.manager.ifNumber, 3)
def scalarGetAndCheck(self, name, value):
self.assertEqual(getattr(self.manager, name),
value)
def testScalar_IpAddress(self):
"""Retrieve IpAdress as a scalar"""
self.scalarGetAndCheck("snimpyIpAddress", "65.65.65.65")
def testScalar_String(self):
"""Retrieve a String as a scalar"""
self.scalarGetAndCheck("snimpyString", "bye")
def testScalar_Integer(self):
"""Retrieve an Integer as a scalar"""
self.scalarGetAndCheck("snimpyInteger", 19)
def testScalar_Enum(self):
"""Retrieve an Enum as a scalar"""
self.scalarGetAndCheck("snimpyEnum", "down")
def testScalar_ObjectId(self):
"""Retrieve an ObjectId as a scalar"""
self.scalarGetAndCheck("snimpyObjectId", (1, 3, 6, 4454, 0, 0))
def testScalar_Boolean(self):
"""Retrieve a Boolean as a scalar"""
self.scalarGetAndCheck("snimpyBoolean", True)
def testScalar_Counter(self):
"""Retrieve a Counter as a scalar"""
self.scalarGetAndCheck("snimpyCounter", 47)
self.scalarGetAndCheck("snimpyCounter64", 2 ** 48 + 3)
def testScalar_Gauge(self):
"""Retrieve a Gauge as a scalar"""
self.scalarGetAndCheck("snimpyGauge", 18)
def testScalar_Timeticks(self):
"""Retrieve a TimeTicks as a scalar"""
self.scalarGetAndCheck(
"snimpyTimeticks",
timedelta(days=1,
hours=9,
minutes=38,
seconds=31))
def testScalar_Bits(self):
"""Retrieve Bits as a scalar"""
self.scalarGetAndCheck("snimpyBits", ["first", "third"])
def testScalar_MacAddress(self):
"""Retrieve MacAddress as a scalar"""
self.scalarGetAndCheck("snimpyMacAddress", "11:12:13:14:15:16")
def testWalkIfTable(self):
"""Test we can walk IF-MIB::ifTable"""
results = [(idx, self.manager.ifDescr[idx], self.manager.ifType[idx])
for idx in self.manager.ifIndex]
self.assertEqual(results,
[(1, "lo", 24),
(2, "eth0", 6),
(3, "eth1", 6)])
def testWalkIfTableWithoutBulk(self):
"""Walk IF-MIB::ifTable without GETBULK"""
self.session.bulk = False
self.testWalkIfTable()
def testWalkComplexIndexes(self):
"""Test if we can walk a table with complex indexes"""
results = [(idx, self.manager.snimpyIndexInt[idx])
for idx in self.manager.snimpyIndexInt]
self.assertEqual(results,
[(("row1", (1, 2, 3),
"alpha5", "end of row1"), 4571),
(("row2", (1, 0, 2, 3),
"beta32", "end of row2"), 78741),
(("row3", (120, 1, 2, 3),
"gamma7", "end of row3"), 4110)])
def testGetInexistentStuff(self):
"""Try to access stuff that does not exist on the agent"""
self.assertRaises(snmp.SNMPNoSuchObject,
getattr, self.manager, "snimpyNotImplemented")
self.assertRaises(snmp.SNMPNoSuchObject,
self.manager.ifName.__getitem__, 47)
self.assertRaises(snmp.SNMPNoSuchInstance,
self.manager.ifDescr.__getitem__, 47)
def testAccessInexistentStuff(self):
"""Try to access stuff that don't exist in MIB"""
self.assertRaises(AttributeError,
getattr, self.manager, "iDoNotExist")
def testAccessIncorrectIndex(self):
"""Try to access with incorrect indexes"""
self.assertRaises(ValueError,
self.manager.ifDescr.__getitem__, (47, 18))
self.assertRaises(ValueError,
self.manager.ifDescr.__getitem__, "nothing")
class TestManagerSet(TestManager):
"""Test setting stuff from manager"""
def testSetScalar(self):
"""Try to set a simple value"""
self.manager.snimpyString = "hello"
self.assertEqual(self.manager.snimpyString, "hello")
def scalarSetAndCheck(self, name, value):
setattr(self.manager, name, value)
self.assertEqual(getattr(self.manager, name),
value)
def testScalar_IpAddress(self):
"""Retrieve IpAdress as a scalar"""
self.scalarSetAndCheck("snimpyIpAddress", "165.255.65.65")
def testScalar_String(self):
"""Retrieve a String as a scalar"""
self.scalarSetAndCheck("snimpyString", "awesome !!!")
def testScalar_Integer(self):
"""Retrieve an Integer as a scalar"""
self.scalarSetAndCheck("snimpyInteger", 1900)
def testScalar_Enum(self):
"""Retrieve an Enum as a scalar"""
self.scalarSetAndCheck("snimpyEnum", "up")
def testScalar_ObjectId(self):
"""Retrieve an ObjectId as a scalar"""
self.scalarSetAndCheck("snimpyObjectId", (1, 3, 6, 4454, 19, 47))
def testScalar_Boolean(self):
"""Retrieve a Boolean as a scalar"""
self.scalarSetAndCheck("snimpyBoolean", False)
def testScalar_Counter(self):
"""Retrieve a Counter as a scalar"""
self.scalarSetAndCheck("snimpyCounter", 4700)
self.scalarSetAndCheck("snimpyCounter64", 2 ** 48 + 3 - 18)
def testScalar_Gauge(self):
"""Retrieve a Gauge as a scalar"""
self.scalarSetAndCheck("snimpyGauge", 180014)
def testScalar_Timeticks(self):
"""Retrieve a TimeTicks as a scalar"""
self.scalarSetAndCheck(
"snimpyTimeticks",
timedelta(days=1,
hours=17,
minutes=38,
seconds=31))
def testScalar_Bits(self):
"""Retrieve Bits as a scalar"""
self.scalarSetAndCheck("snimpyBits", ["first", "second"])
def testScalar_MacAddress(self):
"""Retrieve MAC address as a scala"""
self.scalarSetAndCheck("snimpyMacAddress", "a0:b0:c0:d0:e:ff")
def testNonScalarSet(self):
"""Check we can set a non-scalar value"""
idx = ("row2", (1, 0, 2, 3), "beta32", "end of row2")
self.manager.snimpyIndexInt[idx] = 1041
self.assertEqual(self.manager.snimpyIndexInt[idx], 1041)
def testSetWithContext(self):
"""Set several values atomically (inside a context)"""
with self.manager as m:
m.snimpyString = "Noooooo!"
m.snimpyInteger = 42
self.assertEqual(m.snimpyString, "Noooooo!")
self.assertEqual(m.snimpyInteger, 42)
def testSetWithContextAndAbort(self):
"""Check if writing several values atomically can be aborted"""
try:
with self.manager as m:
m.snimpyString = "Abort sir!"
m.snimpyInteger = 37
raise RuntimeError("Abort now!")
except RuntimeError as e:
self.assertEqual(str(e), "Abort now!")
self.assertNotEqual(m.snimpyString, "Abort sir!")
self.assertNotEqual(m.snimpyInteger, 37)
def testSetInexistentStuff(self):
"""Try to access stuff that does not exist on the agent"""
self.assertRaises(snmp.SNMPNotWritable,
setattr, self.manager, "snimpyNotImplemented",
"Hello")
self.assertRaises(snmp.SNMPNotWritable,
self.manager.ifName.__setitem__, 47, "Wouh")
self.assertRaises(snmp.SNMPNotWritable,
self.manager.ifDescr.__setitem__, 47, "Noooo")
def testAccessInexistentStuff(self):
"""Try to access stuff that don't exist in MIB"""
self.assertRaises(AttributeError,
setattr, self.manager, "iDoNotExist", 47)
def testAccessIncorrectIndex(self):
"""Try to access with incorrect indexes"""
self.assertRaises(ValueError,
self.manager.ifDescr.__setitem__, (47, 18), "Nooo")
self.assertRaises(ValueError,
self.manager.ifDescr.__setitem__,
"nothing", "Neither")
class TestManagerWithNone(TestManagerGet):
"""Test a manager answering None for inexistent stuff"""
def setUp(self):
self.manager = Manager(host="127.0.0.1:{0}".format(self.agent.port),
community="public",
version=2, none=True)
self.session = self.manager._session._session
def testGetInexistentStuff(self):
"""Try to access stuff that does not exist on the agent"""
self.assertEqual(self.manager.snimpyNotImplemented, None)
self.assertEqual(self.manager.ifName[47], None)
self.assertEqual(self.manager.ifDescr[47], None)
class TestCachingManager(TestManagerGet):
"""Test if caching manager works like regular manager"""
def setUp(self):
self.manager = Manager(host="127.0.0.1:{0}".format(self.agent.port),
community="public",
version=2, cache=1)
self.session = self.manager._session._session
class TestCachingManagerWithModificatons(TestManager):
"""Test if caching manager works with modifications"""
def setUp(self):
self.manager = Manager(host="127.0.0.1:{0}".format(self.agent.port),
community="public",
version=2, cache=1)
self.session = self.manager._session._session
def testCacheScalar(self):
"""Check that a scalar value is kept in cache"""
original = self.manager.snimpyString
self.manager.snimpyString = "Nooooo"
self.assertEqual(self.manager.snimpyString, original)
def testCacheNonScalar(self):
"""Check we can cache a non-scalar value"""
idx = ("row2", (1, 0, 2, 3), "beta32", "end of row2")
original = self.manager.snimpyIndexInt[idx]
self.manager.snimpyIndexInt[idx] = 1041
self.assertEqual(self.manager.snimpyIndexInt[idx], original)
def testCacheExpire(self):
"""Check the cache can expire"""
self.manager.snimpyString = "Yeesss"
time.sleep(1)
self.assertEqual(self.manager.snimpyString, "Yeesss")
snimpy-0.8.1/tests/SNIMPY-INVALID-MIB.mib 0000644 0001750 0001750 00000001160 11763715346 020165 0 ustar bernat bernat 0000000 0000000 SNIMPY-INVALID-MIB DEFINITIONS ::= BEGIN
IMPORTS
inexistentNode FROM INEXISTENT-SNIMPY-MIB ;
invalidSnimpy MODULE-IDENTITY
LAST-UPDATED "200809160000Z"
ORGANIZATION
"snimpy
https://github.com/vincentbernat/snimpy"
CONTACT-INFO
"Lorem ipsum, etc, etc."
DESCRIPTION
"This is a test MIB module for snimpy."
REVISION "200809160000Z"
DESCRIPTION "Last revision"
::= { mib-2 45122 }
invalidSnimpyNode OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An integer"
::= { inexistentNode 1 }
END
snimpy-0.8.1/tests/agent.py 0000644 0001750 0001750 00000025356 12225620542 016434 0 ustar bernat bernat 0000000 0000000 from multiprocessing import Process, Queue
import random
from pysnmp.entity import engine, config
from pysnmp.entity.rfc3413 import cmdrsp, context
from pysnmp.carrier.asynsock.dgram import udp
from pysnmp.proto.api import v2c
class TestAgent(object):
"""Agent for testing purpose"""
def __init__(self):
q = Queue()
self._process = Process(target=self._setup, args=(q,))
self._process.start()
self.port = q.get()
def terminate(self):
self._process.terminate()
def _setup(self, q):
"""Setup a new agent in a separate process.
The port the agent is listening too will be returned using the
provided queue.
"""
port = random.randrange(22000, 22989)
snmpEngine = engine.SnmpEngine()
config.addSocketTransport(
snmpEngine,
udp.domainName,
udp.UdpTransport().openServerMode(('127.0.0.1', port)))
# Community is public and MIB is writable
config.addV1System(snmpEngine, 'read-write', 'public')
config.addVacmUser(snmpEngine, 1, 'read-write', 'noAuthNoPriv',
(1, 3, 6), (1, 3, 6))
config.addVacmUser(snmpEngine, 2, 'read-write', 'noAuthNoPriv',
(1, 3, 6), (1, 3, 6))
config.addV3User(
snmpEngine, 'read-write',
config.usmHMACMD5AuthProtocol, 'authpass',
config.usmAesCfb128Protocol, 'privpass'
)
config.addVacmUser(snmpEngine, 3, 'read-write', 'authPriv',
(1, 3, 6), (1, 3, 6))
# Build MIB
def stringToOid(string):
return [ord(x) for x in string]
def flatten(*args):
result = []
for el in args:
if isinstance(el, (list, tuple)):
for sub in el:
result.append(sub)
else:
result.append(el)
return tuple(result)
snmpContext = context.SnmpContext(snmpEngine)
mibBuilder = snmpContext.getMibInstrum().getMibBuilder()
(MibTable, MibTableRow, MibTableColumn,
MibScalar, MibScalarInstance) = mibBuilder.importSymbols(
'SNMPv2-SMI',
'MibTable', 'MibTableRow', 'MibTableColumn',
'MibScalar', 'MibScalarInstance')
mibBuilder.exportSymbols(
'__MY_SNMPv2_MIB',
# SNMPv2-MIB::sysDescr
MibScalar((1, 3, 6, 1, 2, 1, 1, 1), v2c.OctetString()),
MibScalarInstance((1, 3, 6, 1, 2, 1, 1, 1), (0,),
v2c.OctetString("Snimpy Test Agent")))
mibBuilder.exportSymbols(
'__MY_IF_MIB',
# IF-MIB::ifNumber
MibScalar((1, 3, 6, 1, 2, 1, 2, 1), v2c.Integer()),
MibScalarInstance((1, 3, 6, 1, 2, 1, 2, 1), (0,), v2c.Integer(3)),
# IF-MIB::ifTable
MibTable((1, 3, 6, 1, 2, 1, 2, 2)),
MibTableRow((1, 3, 6, 1, 2, 1, 2, 2, 1)).setIndexNames(
(0, '__MY_IF_MIB', 'ifIndex')),
# IF-MIB::ifIndex
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 2, 2, 1, 1), (1,), v2c.Integer(1)),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 2, 2, 1, 1), (2,), v2c.Integer(2)),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 2, 2, 1, 1), (3,), v2c.Integer(3)),
# IF-MIB::ifDescr
MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 2), v2c.OctetString()),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 2, 2, 1, 2), (1,), v2c.OctetString("lo")),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 2, 2, 1, 2), (2,), v2c.OctetString("eth0")),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 2, 2, 1, 2), (3,), v2c.OctetString("eth1")),
# IF-MIB::ifType
MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 3), v2c.Integer()),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 2, 2, 1, 3), (1,), v2c.Integer(24)),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 2, 2, 1, 3), (2,), v2c.Integer(6)),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 2, 2, 1, 3), (3,), v2c.Integer(6)),
# IF-MIB::ifIndex
ifIndex=MibTableColumn((1, 3, 6, 1, 2, 1, 2, 2, 1, 1),
v2c.Integer()))
mibBuilder.exportSymbols(
'__MY_SNIMPY-MIB',
# SNIMPY-MIB::snimpyIpAddress
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 1),
v2c.OctetString()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 1), (0,),
v2c.OctetString("AAAA")),
# SNIMPY-MIB::snimpyString
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 2),
v2c.OctetString()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 2), (0,), v2c.OctetString("bye")),
# SNIMPY-MIB::snimpyInteger
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 3),
v2c.Integer()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 3), (0,), v2c.Integer(19)),
# SNIMPY-MIB::snimpyEnum
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 4),
v2c.Integer()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 4), (0,), v2c.Integer(2)),
# SNIMPY-MIB::snimpyObjectId
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 5),
v2c.ObjectIdentifier()).setMaxAccess("readwrite"),
MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 5), (
0,), v2c.ObjectIdentifier((1, 3, 6, 4454, 0, 0))),
# SNIMPY-MIB::snimpyBoolean
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 6),
v2c.Integer()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 6), (0,), v2c.Integer(1)),
# SNIMPY-MIB::snimpyCounter
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 7),
v2c.Counter32()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 7), (0,), v2c.Counter32(47)),
# SNIMPY-MIB::snimpyGauge
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 8),
v2c.Gauge32()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 8), (0,), v2c.Gauge32(18)),
# SNIMPY-MIB::snimpyTimeticks
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 9),
v2c.TimeTicks()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 9), (0,),
v2c.TimeTicks(12111100)),
# SNIMPY-MIB::snimpyCounter64
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 10),
v2c.Counter64()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 10), (0,),
v2c.Counter64(2 ** 48 + 3)),
# SNIMPY-MIB::snimpyBits
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 11),
v2c.OctetString()).setMaxAccess("readwrite"),
MibScalarInstance(
(1, 3, 6, 1, 2, 1, 45121, 1, 11), (0,),
v2c.OctetString(b"\xa0")),
# SNIMPY-MIB::snimpyMacAddress
MibScalar((1, 3, 6, 1, 2, 1, 45121, 1, 15),
v2c.OctetString()).setMaxAccess("readwrite"),
MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 1, 15), (
0,), v2c.OctetString(b"\x11\x12\x13\x14\x15\x16")),
# SNIMPY-MIB::snimpyIndexTable
MibTable((1, 3, 6, 1, 2, 1, 45121, 2, 3)),
MibTableRow(
(1, 3, 6, 1, 2, 1, 45121, 2, 3, 1)).setIndexNames(
(0, "__MY_SNIMPY-MIB", "snimpyIndexVarLen"),
(0, "__MY_SNIMPY-MIB", "snimpyIndexOidVarLen"),
(0, "__MY_SNIMPY-MIB", "snimpyIndexFixedLen"),
(1, "__MY_SNIMPY-MIB", "snimpyIndexImplied")),
# SNIMPY-MIB::snimpyIndexInt
MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6),
flatten(4, stringToOid('row1'),
3, 1, 2, 3,
stringToOid('alpha5'),
stringToOid('end of row1')),
v2c.Integer(4571)),
MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6),
flatten(4, stringToOid('row2'),
4, 1, 0, 2, 3,
stringToOid('beta32'),
stringToOid('end of row2')),
v2c.Integer(78741)),
MibScalarInstance((1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6),
flatten(4, stringToOid('row3'),
4, 120, 1, 2, 3,
stringToOid('gamma7'),
stringToOid('end of row3')),
v2c.Integer(4110)),
# Indexes
snimpyIndexVarLen=MibTableColumn(
(1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 1),
v2c.OctetString(
)).setMaxAccess("noaccess"),
snimpyIndexIntIndex=MibTableColumn(
(1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 2),
v2c.Integer(
)).setMaxAccess(
"noaccess"),
snimpyIndexOidVarLen=MibTableColumn(
(1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 3),
v2c.ObjectIdentifier(
)).setMaxAccess(
"noaccess"),
snimpyIndexFixedLen=MibTableColumn(
(1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 4),
v2c.OctetString(
).setFixedLength(
6)).setMaxAccess(
"noaccess"),
snimpyIndexImplied=MibTableColumn(
(1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 5),
v2c.OctetString(
)).setMaxAccess("noaccess"),
snimpyIndexInt=MibTableColumn(
(1, 3, 6, 1, 2, 1, 45121, 2, 3, 1, 6),
v2c.Integer()).setMaxAccess("readwrite")
)
# Start agent
cmdrsp.GetCommandResponder(snmpEngine, snmpContext)
cmdrsp.SetCommandResponder(snmpEngine, snmpContext)
cmdrsp.NextCommandResponder(snmpEngine, snmpContext)
cmdrsp.BulkCommandResponder(snmpEngine, snmpContext)
q.put(port)
snmpEngine.transportDispatcher.jobStarted(1)
snmpEngine.transportDispatcher.runDispatcher()
snimpy-0.8.1/tests/test_snmp.py 0000644 0001750 0001750 00000024754 12225620542 017353 0 ustar bernat bernat 0000000 0000000 import unittest
import os
from datetime import timedelta
from snimpy import basictypes, snmp, mib
import agent
class TestSnmpRetriesTimeout(unittest.TestCase):
"""Live modification of retry and timeout values for a session"""
def setUp(self):
self.session = snmp.Session(host="localhost",
community="public",
version=2)
def testGetRetries(self):
"""Get default retries value"""
self.assertEqual(self.session.retries, 5)
def testGetTimeout(self):
"""Get default timeout value"""
self.assertEqual(self.session.timeout, 1000000)
def testSetRetries(self):
"""Try to set a new retry value"""
self.session.retries = 2
self.assertEqual(self.session.retries, 2)
self.session.retries = 0
self.assertEqual(self.session.retries, 0)
def testSetTimeout(self):
"""Try to set a new timeout value"""
self.session.timeout = 500000
self.assertEqual(self.session.timeout, 500000)
def testErrors(self):
"""Try invalid values for timeout and retries"""
self.assertRaises(ValueError, setattr, self.session, "timeout", 0)
self.assertRaises(ValueError, setattr, self.session, "timeout", -30)
self.assertRaises(ValueError, setattr, self.session, "retries", -5)
class TestSnmpSession(unittest.TestCase):
"""Test for session creation using SNMPv1/v2c/v3"""
def testSnmpV1(self):
"""Check initialization of SNMPv1 session"""
snmp.Session(host="localhost",
community="public",
version=1)
def testSnmpV2(self):
"""Check initialization of SNMPv2 session"""
snmp.Session(host="localhost",
community="public",
version=2)
def testSnmpV3(self):
"""Check initialization of SNMPv3 session"""
snmp.Session(host="localhost",
version=3,
secname="readonly",
authprotocol="MD5", authpassword="authpass",
privprotocol="AES", privpassword="privpass")
def testSnmpV3Protocols(self):
"""Check accepted auth and privacy protocols"""
for auth in ["MD5", "SHA"]:
for priv in ["AES", "AES128", "DES"]:
snmp.Session(host="localhost",
version=3,
secname="readonly",
authprotocol=auth, authpassword="authpass",
privprotocol=priv, privpassword="privpass")
self.assertRaises(ValueError,
snmp.Session,
host="localhost",
version=3,
secname="readonly",
authprotocol="NOEXIST", authpassword="authpass",
privprotocol="AES", privpassword="privpass")
self.assertRaises(ValueError,
snmp.Session,
host="localhost",
version=3,
secname="readonly",
authprotocol="MD5", authpassword="authpass",
privprotocol="NOEXIST", privpassword="privpass")
def testSnmpV3SecLevels(self):
"""Check accepted security levels"""
auth = "MD5"
priv = "DES"
snmp.Session(host="localhost",
version=3,
secname="readonly",
authprotocol=auth, authpassword="authpass",
privprotocol=priv, privpassword="privpass")
snmp.Session(host="localhost",
version=3,
secname="readonly",
authprotocol=None,
privprotocol=None)
snmp.Session(host="localhost",
version=3,
secname="readonly",
authprotocol=auth, authpassword="authpass",
privprotocol=None)
class TestSnmp1(unittest.TestCase):
"""
Test communication with an agent with SNMPv1.
"""
version = 1
@classmethod
def setUpClass(cls):
mib.load('IF-MIB')
mib.load('SNMPv2-MIB')
cls.agent = agent.TestAgent()
def setUp(self):
self.session = snmp.Session(
host="127.0.0.1:{0}".format(self.agent.port),
community="public",
version=self.version)
@classmethod
def tearDownClass(cls):
cls.agent.terminate()
def testGetString(self):
"""Get a string value"""
ooid = mib.get('SNMPv2-MIB', 'sysDescr').oid + (0,)
oid, a = self.session.get(ooid)[0]
self.assertEqual(oid, ooid)
self.assertEqual(a, b"Snimpy Test Agent")
def testGetInteger(self):
"""Get an integer value"""
oid, a = self.session.get(mib.get('IF-MIB', 'ifNumber').oid + (0,))[0]
self.assert_(a > 1) # At least lo and another interface
def testGetEnum(self):
"""Get an enum value"""
oid, a = self.session.get(mib.get('IF-MIB', 'ifType').oid + (1,))[0]
self.assertEqual(a, 24) # This is software loopback
b = basictypes.build('IF-MIB', 'ifType', a)
self.assertEqual(b, "softwareLoopback")
def testGetMacAddress(self):
"""Get a MAC address"""
mib.load(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"SNIMPY-MIB.mib"))
oid, a = self.session.get((1, 3, 6, 1, 2, 1, 45121, 1, 15, 0))[0]
self.assertEqual(a, b"\x11\x12\x13\x14\x15\x16")
b = basictypes.build('SNIMPY-MIB', 'snimpyMacAddress', a)
self.assertEqual(b, "11:12:13:14:15:16")
def testInexistant(self):
"""Get an inexistant value"""
self.assertRaises(
self.version == 1 and snmp.SNMPNoSuchName or snmp.SNMPNoSuchObject,
self.session.get,
(1, 2, 3))
def testSetIpAddress(self):
"""Set IpAddress."""
self.setAndCheck('snimpyIpAddress', '10.14.12.12')
def testSetString(self):
"""Set String."""
self.setAndCheck('snimpyString', 'hello')
def testSetInteger(self):
"""Set Integer."""
self.setAndCheck('snimpyInteger', 1574512)
def testSetEnum(self):
"""Set Enum."""
self.setAndCheck('snimpyEnum', 'testing')
def testSetObjectId(self):
"""Set ObjectId."""
self.setAndCheck('snimpyObjectId', (1, 2, 3, 4, 5, 6))
def testSetCounter(self):
"""Set Counter."""
self.setAndCheck('snimpyCounter', 545424)
def testSetGauge(self):
"""Set Gauge."""
self.setAndCheck('snimpyGauge', 4857544)
def testSetBoolean(self):
"""Set Boolean."""
self.setAndCheck('snimpyBoolean', True)
def testSetTimeticks(self):
"""Set Timeticks."""
self.setAndCheck('snimpyTimeticks', timedelta(3, 18))
def testSetBits(self):
"""Set Bits."""
self.setAndCheck('snimpyBits', ["third", "last"])
def testSetMacAddress(self):
"""Set a MAC address."""
self.setAndCheck('snimpyMacAddress', u"a0:b0:c0:d0:e:ff")
oid, a = self.session.get((1, 3, 6, 1, 2, 1, 45121, 1, 15, 0))[0]
# This is software loopback
self.assertEqual(a, b"\xa0\xb0\xc0\xd0\x0e\xff")
def setAndCheck(self, oid, value):
"""Set and check a value"""
mib.load(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"SNIMPY-MIB.mib"))
ooid = mib.get('SNIMPY-MIB', oid).oid + (0,)
self.session.set(ooid,
basictypes.build('SNIMPY-MIB', oid, value))
self.assertEqual(
basictypes.build('SNIMPY-MIB', oid, self.session.get(ooid)[0][1]),
basictypes.build('SNIMPY-MIB', oid, value))
def testMultipleGet(self):
"""Get multiple values at once"""
ooid1 = mib.get('SNMPv2-MIB', 'sysDescr').oid + (0,)
ooid2 = mib.get('IF-MIB', 'ifNumber').oid + (0,)
ooid3 = mib.get('IF-MIB', 'ifType').oid + (1,)
(oid1, a1), (oid2, a2), (oid3, a3) = self.session.get(
ooid1, ooid2, ooid3)
self.assertEqual(oid1, ooid1)
self.assertEqual(oid2, ooid2)
self.assertEqual(oid3, ooid3)
self.assertEqual(a1, b"Snimpy Test Agent")
self.assert_(a2 > 1)
b = basictypes.build('IF-MIB', 'ifType', a3)
self.assertEqual(b, "softwareLoopback")
def testBulk(self):
"""Try to set bulk to different values"""
self.session.bulk = 32
self.assertEqual(self.session.bulk, 32)
self.assertRaises(ValueError,
setattr,
self.session,
"bulk",
0)
self.assertRaises(ValueError,
setattr,
self.session,
"bulk",
-10)
def testWalk(self):
"""Check if we can walk"""
ooid = mib.get("IF-MIB", "ifDescr").oid
results = self.session.walk(ooid)
self.assertEqual(results,
((ooid + (1,), b"lo"),
(ooid + (2,), b"eth0"),
(ooid + (3,), b"eth1")))
class TestSnmp2(TestSnmp1):
"""Test communication with an agent with SNMPv2."""
version = 2
def testSetCounter64(self):
"""Set Counter64."""
self.setAndCheck('snimpyCounter64', 2 ** 47 + 1)
def testWalk(self):
"""Check if we can walk"""
ooid = mib.get("IF-MIB", "ifDescr").oid
self.session.bulk = 4
results = self.session.walk(ooid)
self.assertEqual(results,
((ooid + (1,), b"lo"),
(ooid + (2,), b"eth0"),
(ooid + (3,), b"eth1"),
(mib.get("IF-MIB", "ifType").oid + (1,), 24)))
self.session.bulk = 2
results = self.session.walk(ooid)
self.assertEqual(results[:2],
((ooid + (1,), b"lo"),
(ooid + (2,), b"eth0")))
class TestSnmp3(TestSnmp2):
"""Test communicaton with an agent with SNMPv3."""
version = 3
def setUp(self):
self.session = snmp.Session(
host="127.0.0.1:{0}".format(self.agent.port),
version=3,
secname="read-write",
authprotocol="MD5", authpassword="authpass",
privprotocol="AES", privpassword="privpass")
snimpy-0.8.1/tests/test_mib.py 0000644 0001750 0001750 00000024576 12225620542 017147 0 ustar bernat bernat 0000000 0000000 import unittest
import os
from snimpy import mib, basictypes
class TestMibSnimpy(unittest.TestCase):
def setUp(self):
mib.load(os.path.join(os.path.dirname(os.path.abspath(__file__)),
"SNIMPY-MIB.mib"))
self.nodes = ["snimpy",
"snimpyScalars",
"snimpyTables"]
self.nodes.sort()
self.tables = ["snimpyComplexTable",
"snimpySimpleTable",
"snimpyIndexTable"]
self.tables.sort()
self.columns = ["snimpyComplexFirstIP",
"snimpyComplexSecondIP",
"snimpySimpleIndex",
"snimpyComplexState",
"snimpySimpleDescr",
"snimpySimplePhys",
"snimpySimpleType",
"snimpyIndexVarLen",
"snimpyIndexIntIndex",
"snimpyIndexOidVarLen",
"snimpyIndexFixedLen",
"snimpyIndexImplied",
"snimpyIndexInt",
]
self.columns.sort()
self.scalars = ["snimpyIpAddress",
"snimpyString",
"snimpyInteger",
"snimpyEnum",
"snimpyObjectId",
"snimpyBoolean",
"snimpyCounter",
"snimpyGauge",
"snimpyTimeticks",
"snimpyCounter64",
"snimpyBits",
"snimpyNotImplemented",
"snimpyOctetString",
"snimpyUnicodeString",
"snimpyMacAddress"]
self.scalars.sort()
def tearDown(self):
mib.reset()
def testGetNodes(self):
"""Test that we can get all nodes"""
nodes = mib.getNodes('SNIMPY-MIB')
snodes = sorted([str(a) for a in nodes])
self.assertEqual(self.nodes,
snodes)
for n in nodes:
self.assert_(isinstance(n, mib.Node))
def testGetTables(self):
"""Test that we can get all tables"""
tables = mib.getTables('SNIMPY-MIB')
stables = sorted([str(a) for a in tables])
self.assertEqual(self.tables,
stables)
for n in tables:
self.assert_(isinstance(n, mib.Table))
def testGetColumns(self):
"""Test that we can get all columns"""
columns = mib.getColumns('SNIMPY-MIB')
scolumns = sorted([str(a) for a in columns])
self.assertEqual(self.columns,
scolumns)
for n in columns:
self.assert_(isinstance(n, mib.Column))
def testGetScalars(self):
"""Test that we can get all scalars"""
scalars = mib.getScalars('SNIMPY-MIB')
sscalars = sorted([str(a) for a in scalars])
self.assertEqual(self.scalars, sscalars)
for n in scalars:
self.assert_(isinstance(n, mib.Scalar))
def testGet(self):
"""Test that we can get all named attributes"""
for i in self.scalars:
self.assertEqual(str(mib.get('SNIMPY-MIB', i)), i)
self.assert_(isinstance(mib.get('SNIMPY-MIB', i), mib.Scalar))
for i in self.tables:
self.assertEqual(str(mib.get('SNIMPY-MIB', i)), i)
self.assert_(isinstance(mib.get('SNIMPY-MIB', i), mib.Table))
for i in self.columns:
self.assertEqual(str(mib.get('SNIMPY-MIB', i)), i)
self.assert_(isinstance(mib.get('SNIMPY-MIB', i), mib.Column))
for i in self.nodes:
self.assertEqual(str(mib.get('SNIMPY-MIB', i)), i)
self.assert_(isinstance(mib.get('SNIMPY-MIB', i), mib.Node))
def testTableColumnRelation(self):
"""Test that we can get the column from the table and vice-versa"""
for i in self.tables:
table = mib.get('SNIMPY-MIB', i)
for r in table.columns:
self.assert_(isinstance(r, mib.Column))
self.assertEqual(str(r.table), str(i))
self.assert_(str(r).startswith(str(i).replace("Table", "")))
columns = sorted([str(rr)
for rr in self.columns
if str(rr).startswith(str(i).replace("Table",
""))])
tcolumns = [str(rr) for rr in table.columns]
tcolumns.sort()
self.assertEqual(columns, tcolumns)
for r in self.columns:
column = mib.get('SNIMPY-MIB', r)
table = column.table
self.assert_(isinstance(table, mib.Table))
prefix = str(table).replace("Table", "")
self.assertEqual(prefix, str(r)[:len(prefix)])
def testTypes(self):
"""Test that we get the correct types"""
tt = {"snimpyIpAddress": basictypes.IpAddress,
"snimpyString": basictypes.OctetString,
"snimpyOctetString": basictypes.OctetString,
"snimpyUnicodeString": basictypes.OctetString,
"snimpyMacAddress": basictypes.OctetString,
"snimpyInteger": basictypes.Integer,
"snimpyEnum": basictypes.Enum,
"snimpyObjectId": basictypes.Oid,
"snimpyBoolean": basictypes.Boolean,
"snimpyCounter": basictypes.Unsigned32,
"snimpyGauge": basictypes.Unsigned32,
"snimpyTimeticks": basictypes.Timeticks,
"snimpyCounter64": basictypes.Unsigned64,
"snimpyBits": basictypes.Bits,
"snimpySimpleIndex": basictypes.Integer,
"snimpyComplexFirstIP": basictypes.IpAddress,
"snimpyComplexSecondIP": basictypes.IpAddress,
"snimpyComplexState": basictypes.Enum}
for t in tt:
self.assertEqual(mib.get('SNIMPY-MIB', t).type, tt[t])
def testRanges(self):
tt = {"snimpyIpAddress": 4,
"snimpyString": (0, 255),
"snimpyOctetString": None,
"snimpyInteger": [(6, 18), (20, 23), (27, 1336)],
"snimpyEnum": None,
"snimpyObjectId": None,
"snimpyBoolean": None,
"snimpyCounter": (0, 4294967295),
"snimpyGauge": (0, 4294967295),
"snimpyTimeticks": (0, 4294967295),
"snimpyCounter64": (0, 18446744073709551615),
"snimpyBits": None,
"snimpySimpleIndex": (1, 30),
"snimpyComplexFirstIP": 4,
"snimpyComplexSecondIP": 4,
"snimpyComplexState": None
}
for t in tt:
self.assertEqual(mib.get('SNIMPY-MIB', t).ranges, tt[t])
def testEnums(self):
"""Test that we got the enum values correctly"""
self.assertEqual(mib.get('SNIMPY-MIB', "snimpyInteger").enum, None)
self.assertEqual(mib.get("SNIMPY-MIB", "snimpyEnum").enum,
{1: "up",
2: "down",
3: "testing"})
self.assertEqual(mib.get("SNIMPY-MIB", "snimpyBits").enum,
{0: "first",
1: "second",
2: "third",
7: "last"})
def testIndexes(self):
"""Test that we can retrieve correctly the index of tables"""
self.assertEqual(
[str(i) for i in mib.get("SNIMPY-MIB", "snimpySimpleTable").index],
["snimpySimpleIndex"])
self.assertEqual(
[str(i)
for i in mib.get("SNIMPY-MIB", "snimpyComplexTable").index],
["snimpyComplexFirstIP", "snimpyComplexSecondIP"])
def testImplied(self):
"""Check that we can get implied attribute for a given table"""
self.assertEqual(
mib.get("SNIMPY-MIB",
'snimpySimpleTable').implied,
False)
self.assertEqual(
mib.get("SNIMPY-MIB",
'snimpyComplexTable').implied,
False)
self.assertEqual(
mib.get("SNIMPY-MIB",
'snimpyIndexTable').implied,
True)
def testOid(self):
"""Test that objects are rooted at the correct OID"""
oids = {"snimpy": (1, 3, 6, 1, 2, 1, 45121),
"snimpyScalars": (1, 3, 6, 1, 2, 1, 45121, 1),
"snimpyString": (1, 3, 6, 1, 2, 1, 45121, 1, 2),
"snimpyInteger": (1, 3, 6, 1, 2, 1, 45121, 1, 3),
"snimpyBits": (1, 3, 6, 1, 2, 1, 45121, 1, 11),
"snimpyTables": (1, 3, 6, 1, 2, 1, 45121, 2),
"snimpySimpleTable": (1, 3, 6, 1, 2, 1, 45121, 2, 1),
"snimpySimplePhys": (1, 3, 6, 1, 2, 1, 45121, 2, 1, 1, 4),
"snimpyComplexTable": (1, 3, 6, 1, 2, 1, 45121, 2, 2),
"snimpyComplexState": (1, 3, 6, 1, 2, 1, 45121, 2, 2, 1, 3),
}
for o in oids:
self.assertEqual(mib.get('SNIMPY-MIB', o).oid, oids[o])
def testLoadInexistantModule(self):
"""Check that we get an exception when loading an inexistant module"""
self.assertRaises(mib.SMIException, mib.load, "idontexist.gfdgfdg")
def testLoadInvalidModule(self):
"""Check that an obviously invalid module cannot be loaded"""
path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"SNIMPY-INVALID-MIB.mib")
self.assertRaises(mib.SMIException, mib.load, path)
self.assertRaises(mib.SMIException, mib.getNodes, "SNIMPY-INVALID-MIB")
self.assertRaises(mib.SMIException, mib.get,
"SNIMPY-INVALID-MIB", "invalidSnimpyNode")
def testAccesInexistantModule(self):
"""Check that we get an exception when querying inexistant module"""
self.assertRaises(mib.SMIException, mib.getNodes, "idontexist.kjgf")
self.assertRaises(mib.SMIException, mib.getScalars, "idontexist.kjgf")
self.assertRaises(mib.SMIException, mib.getTables, "idontexist.kjgf")
self.assertRaises(mib.SMIException, mib.getColumns, "idontexist.kjgf")
def testFmt(self):
"""Check that we get FMT from types"""
self.assertEqual(mib.get("SNIMPY-MIB", 'snimpySimplePhys').fmt, "1x:")
self.assertEqual(mib.get("SNIMPY-MIB", 'snimpyInteger').fmt, "d-2")
snimpy-0.8.1/tests/SNIMPY-MIB.mib 0000644 0001750 0001750 00000020703 12224343037 017070 0 ustar bernat bernat 0000000 0000000 SNIMPY-MIB DEFINITIONS ::= BEGIN
IMPORTS
MODULE-IDENTITY, OBJECT-TYPE,
IpAddress, Integer32, Gauge32,
TimeTicks, Counter64,
Counter32, mib-2 FROM SNMPv2-SMI
DisplayString, TEXTUAL-CONVENTION,
PhysAddress, TruthValue FROM SNMPv2-TC
IANAifType FROM IANAifType-MIB;
snimpy MODULE-IDENTITY
LAST-UPDATED "200809160000Z"
ORGANIZATION
"snimpy
https://github.com/vincentbernat/snimpy"
CONTACT-INFO
"Lorem ipsum, etc, etc."
DESCRIPTION
"This is a test MIB module for snimpy."
REVISION "200809160000Z"
DESCRIPTION "Last revision"
::= { mib-2 45121 }
OddInteger ::= TEXTUAL-CONVENTION
DISPLAY-HINT "d-2"
STATUS current
DESCRIPTION
"Testing fmt"
SYNTAX INTEGER (6..18 | 20..23 | 27 | 28..1336)
UnicodeString ::= TEXTUAL-CONVENTION
DISPLAY-HINT "255t"
STATUS current
DESCRIPTION
"Testing fmt"
SYNTAX OCTET STRING (SIZE(0..255))
snimpyScalars OBJECT IDENTIFIER ::= { snimpy 1 }
snimpyTables OBJECT IDENTIFIER ::= { snimpy 2 }
snimpyIpAddress OBJECT-TYPE
SYNTAX IpAddress
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An IP address"
::= { snimpyScalars 1 }
snimpyString OBJECT-TYPE
SYNTAX DisplayString (SIZE (0..255))
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An string to display"
::= { snimpyScalars 2 }
snimpyInteger OBJECT-TYPE
SYNTAX OddInteger
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An integer"
::= { snimpyScalars 3 }
snimpyEnum OBJECT-TYPE
SYNTAX INTEGER {
up(1),
down(2),
testing(3)
}
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An enumeration"
::= { snimpyScalars 4 }
snimpyObjectId OBJECT-TYPE
SYNTAX OBJECT IDENTIFIER
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An oid"
::= { snimpyScalars 5 }
snimpyBoolean OBJECT-TYPE
SYNTAX TruthValue
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"A boolean"
::= { snimpyScalars 6 }
snimpyCounter OBJECT-TYPE
SYNTAX Counter32
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"A 32 bits counter"
::= { snimpyScalars 7 }
snimpyGauge OBJECT-TYPE
SYNTAX Gauge32
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"A 32 bits gauge"
::= { snimpyScalars 8 }
snimpyTimeticks OBJECT-TYPE
SYNTAX TimeTicks
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"A timetick"
::= { snimpyScalars 9 }
snimpyCounter64 OBJECT-TYPE
SYNTAX Counter64
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"A 64-bit counter"
::= { snimpyScalars 10 }
snimpyBits OBJECT-TYPE
SYNTAX BITS {
first(0),
second(1),
third(2),
last(7)
}
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"A bit field"
::= { snimpyScalars 11 }
snimpyNotImplemented OBJECT-TYPE
SYNTAX DisplayString (SIZE (0..255))
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An string to display (not implemented)"
::= { snimpyScalars 12 }
snimpyOctetString OBJECT-TYPE
SYNTAX OCTET STRING
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An string to display"
::= { snimpyScalars 13 }
snimpyUnicodeString OBJECT-TYPE
SYNTAX UnicodeString
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An unicode string to display"
::= { snimpyScalars 14 }
snimpyMacAddress OBJECT-TYPE
SYNTAX PhysAddress
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"A MAC address"
::= { snimpyScalars 15 }
-- A simple table
snimpySimpleTable OBJECT-TYPE
SYNTAX SEQUENCE OF SnimpySimpleEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A table"
::= { snimpyTables 1 }
snimpySimpleEntry OBJECT-TYPE
SYNTAX SnimpySimpleEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"Entry for our simple table"
INDEX { snimpySimpleIndex }
::= { snimpySimpleTable 1 }
SnimpySimpleEntry ::=
SEQUENCE {
snimpySimpleIndex Integer32,
snimpySimpleDescr DisplayString,
snimpySimpleType IANAifType,
snimpySimplePhys PhysAddress
}
snimpySimpleIndex OBJECT-TYPE
SYNTAX Integer32 (1..30)
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"Index for snimpy simple table"
::= { snimpySimpleEntry 1 }
snimpySimpleDescr OBJECT-TYPE
SYNTAX DisplayString (SIZE (0..255))
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"Blah blah"
::= { snimpySimpleEntry 2 }
snimpySimpleType OBJECT-TYPE
SYNTAX IANAifType
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"Blah blah"
::= { snimpySimpleEntry 3 }
snimpySimplePhys OBJECT-TYPE
SYNTAX PhysAddress
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"Blah blah"
::= { snimpySimpleEntry 4 }
-- A more complex table
snimpyComplexTable OBJECT-TYPE
SYNTAX SEQUENCE OF SnimpyComplexEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A more complex table"
::= { snimpyTables 2 }
snimpyComplexEntry OBJECT-TYPE
SYNTAX SnimpyComplexEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"Entry for our complex table"
INDEX { snimpyComplexFirstIP, snimpyComplexSecondIP }
::= { snimpyComplexTable 1 }
SnimpyComplexEntry ::=
SEQUENCE {
snimpyComplexFirstIP IpAddress,
snimpyComplexSecondIP IpAddress,
snimpyComplexState INTEGER
}
snimpyComplexFirstIP OBJECT-TYPE
SYNTAX IpAddress
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"First IP address for index"
::= { snimpyComplexEntry 1 }
snimpyComplexSecondIP OBJECT-TYPE
SYNTAX IpAddress
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"Second IP address for index"
::= { snimpyComplexEntry 2 }
snimpyComplexState OBJECT-TYPE
SYNTAX INTEGER {
up(1),
down(2),
testing(3)
}
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"State for our both IP"
::= { snimpyComplexEntry 3 }
-- A table with complex indexes
snimpyIndexTable OBJECT-TYPE
SYNTAX SEQUENCE OF SnimpyIndexEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A table with complex indexes"
::= { snimpyTables 3 }
snimpyIndexEntry OBJECT-TYPE
SYNTAX SnimpyIndexEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"Entry for our indexed table"
INDEX { snimpyIndexVarLen, snimpyIndexOidVarLen,
snimpyIndexFixedLen, IMPLIED snimpyIndexImplied }
::= { snimpyIndexTable 1 }
SnimpyIndexEntry ::=
SEQUENCE {
snimpyIndexVarLen DisplayString,
snimpyIndexIntIndex Integer32,
snimpyIndexOidVarLen OBJECT IDENTIFIER,
snimpyIndexFixedLen DisplayString,
snimpyIndexImplied DisplayString,
snimpyIndexInt Integer32
}
snimpyIndexVarLen OBJECT-TYPE
SYNTAX DisplayString (SIZE (1..10))
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"Variable length index"
::= { snimpyIndexEntry 1 }
snimpyIndexIntIndex OBJECT-TYPE
SYNTAX Integer32
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"Integer index"
::= { snimpyIndexEntry 2 }
snimpyIndexOidVarLen OBJECT-TYPE
SYNTAX OBJECT IDENTIFIER
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"OID as index"
::= { snimpyIndexEntry 3 }
snimpyIndexFixedLen OBJECT-TYPE
SYNTAX DisplayString (SIZE (6))
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"Fixed length index"
::= { snimpyIndexEntry 4 }
snimpyIndexImplied OBJECT-TYPE
SYNTAX DisplayString (SIZE (1..30))
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"Variable length index, implied"
::= { snimpyIndexEntry 5 }
snimpyIndexInt OBJECT-TYPE
SYNTAX Integer32
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"An integer of fixed size"
::= { snimpyIndexEntry 6 }
END
snimpy-0.8.1/setup.cfg 0000644 0001750 0001750 00000000073 12232211722 015422 0 ustar bernat bernat 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
snimpy-0.8.1/MANIFEST.in 0000644 0001750 0001750 00000000641 12232055305 015343 0 ustar bernat bernat 0000000 0000000 recursive-include examples *.py
recursive-include tests *.py SNIMPY-MIB.mib SNIMPY-INVALID-MIB.mib
# Documentation
include man/snimpy.1
include AUTHORS.rst
include CONTRIBUTING.rst
include HISTORY.rst
include README.rst
recursive-include docs *.rst *.py
recursive-include docs/_themes *
recursive-include docs/_static *
# Remove CFFI files
global-exclude __pycache__/*
# Remove git directories
global-exclude .git
snimpy-0.8.1/README.rst 0000644 0001750 0001750 00000003445 12226621215 015303 0 ustar bernat bernat 0000000 0000000 ===============================
snimpy
===============================
.. image:: https://badge.fury.io/py/snimpy.png
:target: http://badge.fury.io/py/snimpy
.. image:: https://travis-ci.org/vincentbernat/snimpy.png?branch=master
:target: https://travis-ci.org/vincentbernat/snimpy
.. image:: https://pypip.in/d/snimpy/badge.png
:target: https://crate.io/packages/snimpy?version=latest
Interactive SNMP tool.
*Snimpy* is a Python-based tool providing a simple interface to build
SNMP query. Here is a very simplistic example that allows us to
display the routing table of a given host::
load("IP-FORWARD-MIB")
m=M("localhost", "public", 2)
routes = m.ipCidrRouteNextHop
for x in routes:
net, netmask, tos, src = x
print("%15s/%-15s via %-15s src %-15s" % (net, netmask, routes[x], src))
You can either use *Snimpy* interactively throught its console
(derived from Python own console or from IPython_ if available) or
write *Snimpy* scripts which are just Python scripts with some global
variables available.
.. _IPython: http://ipython.org
* Free software: ISC license
* Documentation: http://snimpy.rtfd.org.
Features
--------
*Snimpy* is aimed at being the more Pythonic possible. You should forget
that you are doing SNMP requests. *Snimpy* will rely on MIB to hide SNMP
details. Here are some "features":
* MIB parser based on libsmi (through CFFI)
* SNMP requests are handled by PySNMP (SNMPv1, SNMPv2 and SNMPv3
support)
* scalars are just attributes of your session object
* columns are like a Python dictionary and made available as an
attribute
* getting an attribute is like issuing a GET method
* setting an attribute is like issuing a SET method
* iterating over a table is like using GETNEXT
* when something goes wrong, you get an exception