Skip to content

Commit 940e75c

Browse files
committed
Add tests for PAN-OS XML API.
1 parent 2e49e4d commit 940e75c

10 files changed

+646
-0
lines changed

tests/README.rst

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
pan-python Tests
2+
================
3+
4+
``pan-python`` tests use the Python `unit testing framework
5+
<https://docs.python.org/3/library/unittest.html>`_.
6+
7+
Test Naming
8+
-----------
9+
10+
Tests can be created for any of the components in ``pan-python`` using
11+
the following naming convention:
12+
13+
==================== ==================
14+
pan-python Component Test Script Prefix
15+
==================== ==================
16+
PAN-OS XML API *test_xapi_*
17+
PAN-OS Licensing API *test_licapi_*
18+
WildFire API *test_wfapi_*
19+
AutoFocus API *test_afapi_*
20+
==================== ==================
21+
22+
Tests are currently available for the following components:
23+
24+
- PAN-OS XML API
25+
26+
PAN-OS XML API Tests
27+
--------------------
28+
29+
A lab PAN-OS firewall or Panorama are required to run tests.
30+
31+
PAN-OS XML API tests can be performed on the following targets:
32+
33+
================================================== ===========
34+
Test Target Target Name
35+
================================================== ===========
36+
PAN-OS firewall **fw**
37+
Panorama **pano**
38+
PAN-OS firewall via Panorama to device redirection **tgt**
39+
================================================== ===========
40+
41+
Test scripts use the following naming convention to define the valid
42+
test targets:
43+
44+
=============== ======================== ============
45+
Target Names Test Script Prefix Test Targets
46+
=============== ======================== ============
47+
**fw** *test_xapi_fw_* PAN-OS firewall only
48+
**pano** *test_xapi_pano_* Panorama only
49+
**fw_pano** *test_xapi_fw_pano_* PAN-OS firewall or Panorama
50+
**fw_tgt** *test_xapi_fw_tgt_* PAN-OS firewall or firewall via Panorama to device redirection
51+
**fw_tgt_pano** *test_xapi_fw_tgt_pano_* PAN-OS firewall, Panorama or firewall via Panorama to device redirection
52+
=============== ======================== ============
53+
54+
Specifying Test Target
55+
~~~~~~~~~~~~~~~~~~~~~~
56+
57+
A `.panrc file
58+
<https://github.com/kevinsteves/pan-python/blob/master/doc/panrc.rst>`_
59+
is used to reference the test target by a *tagname*.
60+
61+
Export the ``XAPI_TAG`` environment variable with the *tagname* to
62+
use from a .panrc file:
63+
::
64+
65+
$ export XAPI_TAG=vm-50-3
66+
67+
Panorama to Device Redirection
68+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69+
70+
Panorama to device redirection can be used to perform XML API
71+
requests to connected devices via Panorama using the ``target=serial``
72+
API request argument. To execute tests using Panorama to device
73+
redirection the ``.panrc`` *hostname* specifies the Panorama and
74+
the *serial* specifies the connected device:
75+
::
76+
77+
$ grep vm-50-3-panorama .panrc
78+
# vm-50-3-panorama
79+
hostname%vm-50-3-panorama=172.25.2.100
80+
api_username%vm-50-3-panorama=admin
81+
api_password%vm-50-3-panorama=password
82+
serial%vm-50-3-panorama=015351000046877
83+
84+
Run Tests
85+
~~~~~~~~~
86+
87+
To run tests from the top-level directory:
88+
::
89+
90+
$ python3 -m unittest discover -v -s tests -t . -p TARGET-PATTERN
91+
92+
To run tests from the ``tests/`` directory:
93+
::
94+
95+
$ python3 -m unittest discover -v -t .. -p TARGET-PATTERN
96+
97+
Examples
98+
~~~~~~~~
99+
100+
From the ``tests/`` directory run tests to a Panorama target:
101+
::
102+
103+
$ XAPI_TAG=panorama-2 python3 -m unittest discover -v -t .. -p 'test_xapi_*pano*'
104+
test_01 (tests.test_xapi_fw_tgt_pano_export.PanXapiTest) ... ok
105+
test_01 (tests.test_xapi_fw_tgt_pano_log.PanXapiTest) ... ok
106+
test_02 (tests.test_xapi_fw_tgt_pano_log.PanXapiTest) ... ok
107+
test_01 (tests.test_xapi_fw_tgt_pano_op.PanXapiTest) ... ok
108+
test_02 (tests.test_xapi_fw_tgt_pano_op.PanXapiTest) ... ok
109+
test_01 (tests.test_xapi_fw_tgt_pano_report.PanXapiTest) ... ok
110+
test_01 (tests.test_xapi_fw_tgt_pano_user_id.PanXapiTest) ... ok
111+
test_02 (tests.test_xapi_fw_tgt_pano_user_id.PanXapiTest) ... ok
112+
113+
----------------------------------------------------------------------
114+
Ran 8 tests in 6.184s
115+
116+
OK
117+
118+
From the ``tests/`` directory run tests to a firewall target:
119+
::
120+
121+
$ XAPI_TAG=vm-50-3 python3 -m unittest discover -v -t .. -p 'test_xapi_*fw*'
122+
test_01 (tests.test_xapi_fw_tgt_config.PanXapiTest) ... ok
123+
test_01 (tests.test_xapi_fw_tgt_multi_config.PanXapiTest) ... ok
124+
test_02 (tests.test_xapi_fw_tgt_multi_config.PanXapiTest) ... ok
125+
test_01 (tests.test_xapi_fw_tgt_pano_export.PanXapiTest) ... ok
126+
test_01 (tests.test_xapi_fw_tgt_pano_log.PanXapiTest) ... ok
127+
test_02 (tests.test_xapi_fw_tgt_pano_log.PanXapiTest) ... ok
128+
test_01 (tests.test_xapi_fw_tgt_pano_op.PanXapiTest) ... ok
129+
test_02 (tests.test_xapi_fw_tgt_pano_op.PanXapiTest) ... ok
130+
test_01 (tests.test_xapi_fw_tgt_pano_report.PanXapiTest) ... ok
131+
test_01 (tests.test_xapi_fw_tgt_pano_user_id.PanXapiTest) ... ok
132+
test_02 (tests.test_xapi_fw_tgt_pano_user_id.PanXapiTest) ... ok
133+
134+
----------------------------------------------------------------------
135+
Ran 11 tests in 19.983s
136+
137+
OK

tests/__init__.py

Whitespace-only changes.

tests/test_xapi_fw_tgt_config.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os
2+
import sys
3+
import unittest
4+
5+
from . import xapi_mixin
6+
7+
libpath = os.path.dirname(os.path.abspath(__file__))
8+
sys.path[:0] = [os.path.join(libpath, os.pardir, 'lib')]
9+
import pan.xapi
10+
11+
BASE_XPATH = ("/config/devices/entry[@name='localhost.localdomain']"
12+
"/vsys/entry[@name='vsys1']/address")
13+
XPATH_ADDR = BASE_XPATH + "/entry[@name='%s']"
14+
IP_ADDRESS = '192.0.2.1/32'
15+
ELEMENT = '<ip-netmask>%s</ip-netmask>' % IP_ADDRESS
16+
ELEMENT_PATH = './result/entry/ip-netmask'
17+
18+
19+
class PanXapiTest(xapi_mixin.Mixin, unittest.TestCase):
20+
def test_01(self):
21+
address = self.name('address', 16)
22+
xpath = XPATH_ADDR % address
23+
24+
self.api.get(xpath=xpath)
25+
self.assertEqual(self.api.status, 'success')
26+
self.assertEqual(self.api.status_code, '7')
27+
x = self.api.element_root.find('./result')
28+
self.assertIsNotNone(x)
29+
self.assertEqual(len(x), 0)
30+
31+
with self.assertRaises(pan.xapi.PanXapiError) as e:
32+
self.api.show(xpath=xpath)
33+
self.assertEqual(self.api.status, 'error')
34+
msg = 'No such node'
35+
self.assertEqual(str(e.exception), msg)
36+
self.assertEqual(self.api.status_detail, msg)
37+
38+
self.api.set(element=ELEMENT, xpath=xpath)
39+
self.assertEqual(self.api.status, 'success')
40+
41+
self.api.get(xpath=xpath)
42+
self.assertEqual(self.api.status, 'success')
43+
self.assertEqual(self.api.status_code, '19')
44+
x = self.api.element_root.find(ELEMENT_PATH)
45+
self.assertIsNotNone(x)
46+
self.assertEqual(x.text, IP_ADDRESS)
47+
48+
address1 = address + '-1'
49+
self.api.clone(xpath_from=xpath,
50+
xpath=BASE_XPATH,
51+
newname=address1)
52+
self.assertEqual(self.api.status, 'success')
53+
54+
xpath1 = XPATH_ADDR % address1
55+
self.api.get(xpath=xpath)
56+
self.assertEqual(self.api.status, 'success')
57+
self.assertEqual(self.api.status_code, '19')
58+
x = self.api.element_root.find(ELEMENT_PATH)
59+
self.assertIsNotNone(x)
60+
self.assertEqual(x.text, IP_ADDRESS)
61+
62+
address2 = address + '-2'
63+
self.api.rename(xpath=xpath1,
64+
newname=address2)
65+
self.assertEqual(self.api.status, 'success')
66+
67+
xpath2 = XPATH_ADDR % address2
68+
self.api.get(xpath=xpath)
69+
self.assertEqual(self.api.status, 'success')
70+
self.assertEqual(self.api.status_code, '19')
71+
x = self.api.element_root.find(ELEMENT_PATH)
72+
self.assertIsNotNone(x)
73+
self.assertEqual(x.text, IP_ADDRESS)
74+
75+
for path in [xpath, xpath2]:
76+
self.api.delete(xpath=path)
77+
self.assertEqual(self.api.status, 'success')
78+
79+
for path in [xpath, xpath2]:
80+
self.api.get(xpath=path)
81+
self.assertEqual(self.api.status, 'success')
82+
self.assertEqual(self.api.status_code, '7')
83+
x = self.api.element_root.find('./result')
84+
self.assertIsNotNone(x)
85+
self.assertEqual(len(x), 0)
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import os
2+
import sys
3+
import unittest
4+
import xml.etree.ElementTree as etree
5+
6+
from . import xapi_mixin
7+
8+
libpath = os.path.dirname(os.path.abspath(__file__))
9+
sys.path[:0] = [os.path.join(libpath, os.pardir, 'lib')]
10+
import pan.xapi
11+
12+
BASE_XPATH = ("/config/devices/entry[@name='localhost.localdomain']"
13+
"/vsys/entry[@name='vsys1']/address")
14+
XPATH_ADDR = BASE_XPATH + "/entry[@name='%s']"
15+
IP_ADDRESS1 = '192.0.2.1/32'
16+
IP_ADDRESS2 = '192.0.2.2/32'
17+
IP_ADDRESS3 = '192.0.2.3/32'
18+
ELEMENT_PATH = './result/entry/ip-netmask'
19+
20+
21+
def multi_config(actions):
22+
root = etree.Element('multi-config')
23+
id = 100
24+
for _action, attributes, element in actions:
25+
if 'id' not in attributes:
26+
id += 1
27+
attributes.update({'id': str(id)})
28+
action = etree.SubElement(root, _action, attributes)
29+
if element is not None:
30+
action.append(element)
31+
32+
document = etree.tostring(root, encoding='UTF-8')
33+
34+
return document
35+
36+
37+
class PanXapiTest(xapi_mixin.Mixin, unittest.TestCase):
38+
def test_01(self):
39+
address = self.name('address', 16)
40+
xpath = XPATH_ADDR % address
41+
element = etree.Element('ip-netmask')
42+
element.text = IP_ADDRESS1
43+
44+
actions = [
45+
('set', {'xpath': xpath}, element),
46+
]
47+
document = multi_config(actions)
48+
self.api.multi_config(element=document)
49+
self.assertEqual(self.api.status, 'success', msg=document)
50+
51+
self.api.get(xpath=xpath)
52+
self.assertEqual(self.api.status, 'success')
53+
self.assertEqual(self.api.status_code, '19')
54+
x = self.api.element_root.find(ELEMENT_PATH)
55+
self.assertIsNotNone(x)
56+
self.assertEqual(x.text, IP_ADDRESS1)
57+
58+
actions = [
59+
('delete', {'xpath': xpath}, None),
60+
]
61+
document = multi_config(actions)
62+
self.api.multi_config(element=document)
63+
self.assertEqual(self.api.status, 'success', msg=document)
64+
65+
self.api.get(xpath=xpath)
66+
self.assertEqual(self.api.status, 'success')
67+
self.assertEqual(self.api.status_code, '7')
68+
x = self.api.element_root.find('./result')
69+
self.assertIsNotNone(x)
70+
self.assertEqual(len(x), 0)
71+
72+
def test_02(self):
73+
# set addr1, then set addr2 (valid), addr3 (invalid).
74+
# with strict-transactional, addr1 rolled back,
75+
# addr2 rolled back due to addr3 error.
76+
77+
address1 = self.name('address1', 16)
78+
xpath1 = XPATH_ADDR % address1
79+
element1 = etree.Element('ip-netmask')
80+
element1.text = IP_ADDRESS1
81+
82+
actions = [
83+
('set', {'xpath': xpath1}, element1),
84+
]
85+
document = multi_config(actions)
86+
self.api.multi_config(element=document)
87+
self.assertEqual(self.api.status, 'success', msg=document)
88+
89+
self.api.get(xpath=xpath1)
90+
self.assertEqual(self.api.status, 'success')
91+
self.assertEqual(self.api.status_code, '19')
92+
x = self.api.element_root.find(ELEMENT_PATH)
93+
self.assertIsNotNone(x)
94+
self.assertEqual(x.text, IP_ADDRESS1)
95+
96+
address2 = self.name('address2', 16)
97+
xpath2 = XPATH_ADDR % address2
98+
element2 = etree.Element('ip-netmask')
99+
element2.text = IP_ADDRESS2
100+
101+
address3 = self.name('address3', 32) # error, max length 63
102+
xpath3 = XPATH_ADDR % address3
103+
element3 = etree.Element('ip-netmask')
104+
element3.text = IP_ADDRESS3
105+
106+
actions = [
107+
('set', {'xpath': xpath2}, element2),
108+
('set', {'xpath': xpath3,
109+
'id': 'LEN-ERROR'}, element3), # error
110+
]
111+
document = multi_config(actions)
112+
# XXX tgt bug: PAN-196392
113+
with self.assertRaises(pan.xapi.PanXapiError) as e:
114+
self.api.multi_config(element=document, strict=True)
115+
self.assertEqual(self.api.status, 'error', msg=document)
116+
msg = ('status="error" code="12" id="LEN-ERROR" %s '
117+
'Node can be at most 63 characters, but current length: 64')
118+
msg = msg % address3
119+
self.assertIn(msg, self.api.status_detail)
120+
121+
for xpath in [xpath1, xpath2, xpath3]:
122+
self.api.get(xpath=xpath)
123+
self.assertEqual(self.api.status, 'success')
124+
self.assertEqual(self.api.status_code, '7')
125+
x = self.api.element_root.find('./result')
126+
self.assertIsNotNone(x)
127+
self.assertEqual(len(x), 0)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import os
2+
import sys
3+
import unittest
4+
5+
from . import xapi_mixin
6+
7+
libpath = os.path.dirname(os.path.abspath(__file__))
8+
sys.path[:0] = [os.path.join(libpath, os.pardir, 'lib')]
9+
import pan.xapi
10+
11+
12+
class PanXapiTest(xapi_mixin.Mixin, unittest.TestCase):
13+
def test_01(self):
14+
self.api.export(category='configuration')
15+
self.assertEqual(self.api.status, 'success')
16+
x = self.api.element_root.find('./mgt-config')
17+
self.assertIsNotNone(x)

0 commit comments

Comments
 (0)