Skip to content

Commit 7401995

Browse files
committed
Merge branch 'release/0.6.0'
2 parents 36e375c + 30457f8 commit 7401995

File tree

11 files changed

+369
-134
lines changed

11 files changed

+369
-134
lines changed

HISTORY.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33
History
44
=======
55

6+
0.6.0
7+
-----
8+
9+
Released: 2018-03-16
10+
11+
Status: Alpha
12+
13+
- Added initial support for templates and template stacks
14+
- Added: Support for timeouts for logins in user-id module
15+
- Added: `panorama.Template`
16+
- Added: `panorama.TemplateStack`
17+
- Fix: Vsys native objects added under a Panorama will be put in `shared` scope
18+
19+
620
0.5.3
721
-----
822

pandevice/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
__author__ = 'Palo Alto Networks'
2525
__email__ = '[email protected]'
26-
__version__ = '0.5.3'
26+
__version__ = '0.6.0'
2727

2828

2929
import logging

pandevice/base.py

Lines changed: 90 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,10 @@ def vsys(self):
141141
if self.parent is not None:
142142
vsys = self.parent.vsys
143143
if vsys is None and self.ROOT == Root.VSYS:
144-
return 'vsys1'
144+
return getattr(self.parent, 'DEFAULT_VSYS', None)
145145
else:
146146
return vsys
147147

148-
149148
@vsys.setter
150149
def vsys(self, value):
151150
raise err.PanDeviceError("Cannot set vsys on non-vsys object")
@@ -270,60 +269,92 @@ def removeall(self, cls=None):
270269
self.children = []
271270
return children
272271

273-
def xpath(self):
272+
def xpath(self, root=None):
274273
"""Return the full xpath for this object
275274
276275
Xpath in the form: parent's xpath + this object's xpath + entry or member if applicable.
277276
277+
Args:
278+
root: The root to use for this object (default: this object's root)
279+
278280
Returns:
279281
str: The full xpath to this object
280282
281283
"""
282-
xpath = self._parent_xpath() + self.XPATH
283-
if self.SUFFIX is not None:
284-
xpath += self.SUFFIX % (self.uid, )
285-
286-
return xpath
284+
path = []
285+
p = self
286+
if root is None:
287+
root = self.ROOT
288+
vsys = self.vsys
289+
label = getattr(self, 'VSYS_LABEL', 'vsys')
290+
while True:
291+
if isinstance(p, PanDevice) and p != self:
292+
# Stop on the first pandevice encountered, unless the
293+
# pandevice.PanDevice object is the object whose xpath
294+
# was asked for.
295+
path.insert(0, p.xpath_root(root, vsys, label))
296+
break
297+
elif not hasattr(p, 'VSYS_LABEL') or p == self:
298+
# Add on the xpath of this object, unless it is a
299+
# device.Vsys, unless the device.Vsys is the object whose
300+
# xpath was asked for.
301+
addon = p.XPATH
302+
if p.SUFFIX is not None:
303+
addon += p.SUFFIX % (p.uid, )
304+
path.insert(0, addon)
305+
if p.__class__.__name__ == 'Firewall' and p.parent is not None:
306+
if p.parent.__class__.__name__ == 'DeviceGroup':
307+
root = Root.VSYS
308+
p = p.parent
309+
if p is None:
310+
break
311+
if hasattr(p, 'VSYS_LABEL'):
312+
# Either panorama.DeviceGroup or device.Vsys.
313+
label = p.VSYS_LABEL
314+
vsys = p.vsys
315+
elif p.__class__.__name__ == 'Template':
316+
# Hit a template, make sure that the appropriate /config/...
317+
# xpath has been saved.
318+
if not path[0].startswith('/config/'):
319+
path.insert(0, self.xpath_root(root, vsys, label))
320+
vsys = p.vsys
321+
root = p.ROOT
322+
323+
return ''.join(path)
287324

288325
def xpath_nosuffix(self):
289326
"""Return the xpath without the suffix
290327
328+
This is used by refreshall().
329+
291330
Returns:
292331
str: The xpath without entry or member on the end
293332
294333
"""
295-
xpath = self._parent_xpath() + self.XPATH
296-
return xpath
334+
if self.SUFFIX is None:
335+
return self.xpath()
336+
else:
337+
return self.xpath_short()
297338

298-
def xpath_short(self):
339+
def xpath_short(self, root=None):
299340
"""Return an xpath for this object without the final segment
300341
301342
Xpath in the form: parent's xpath + this object's xpath. Used for set API calls.
302343
344+
Args:
345+
root: The root to use for this object (default: this object's root)
346+
303347
Returns:
304348
str: The xpath without the final segment
305349
306350
"""
307-
xpath = self._parent_xpath() + self.XPATH
308-
if self.SUFFIX is None:
309-
# Remove last segment of xpath
310-
xpath = re.sub(r"/(?=[^/']*'[^']*'[^/']*$|[^/]*$).*$", "", xpath)
351+
xpath = self.xpath(root)
352+
xpath = re.sub(r"/(?=[^/']*'[^']*'[^/']*$|[^/]*$).*$", "", xpath)
311353
return xpath
312354

313-
def _parent_xpath(self):
314-
if self.parent is None:
315-
return ""
316-
else:
317-
return self.parent._build_xpath(self.ROOT, None)
318-
319-
def _build_xpath(self, root, vsys):
320-
if self.parent is None:
321-
# self with no parent
322-
return ""
323-
parent_xpath = self.parent._build_xpath(root, vsys) + self.XPATH
324-
if self.SUFFIX is not None:
325-
parent_xpath += self.SUFFIX % (self.uid, )
326-
return parent_xpath
355+
def xpath_root(self, root_type, vsys, label='vsys'):
356+
if self.parent:
357+
return self.parent.xpath_root(root_type, vsys, label)
327358

328359
def xpath_vsys(self):
329360
if self.parent is not None:
@@ -333,12 +364,13 @@ def xpath_panorama(self):
333364
if self.parent is not None:
334365
return self.parent.xpath_panorama()
335366

336-
def _root_xpath_vsys(self, vsys):
367+
def _root_xpath_vsys(self, vsys, label='vsys'):
337368
if vsys == 'shared':
338-
return '/config/shared'
369+
xpath = '/config/shared'
370+
else:
371+
xpath = "/config/devices/entry[@name='localhost.localdomain']"
372+
xpath += "/{0}/entry[@name='{1}']".format(label, vsys or 'vsys1')
339373

340-
xpath = "/config/devices/entry[@name='localhost.localdomain']"
341-
xpath += "/vsys/entry[@name='{0}']".format(vsys or 'vsys1')
342374
return xpath
343375

344376
def element(self, with_children=True, comparable=False):
@@ -1008,27 +1040,6 @@ def find_index(self, name=None, class_type=None):
10081040
and type(child) == class_type):
10091041
return num
10101042

1011-
@classmethod
1012-
def applyall(cls, parent):
1013-
device = parent.nearest_pandevice()
1014-
logger.debug(device.id + ": applyall called on %s type" % cls)
1015-
objects = parent.findall(cls)
1016-
if not objects:
1017-
return
1018-
# Create the xpath
1019-
xpath = objects[0].xpath_nosuffix()
1020-
# Create the root element
1021-
lasttag = cls.XPATH.rsplit("/", 1)[-1]
1022-
element = ET.Element(lasttag)
1023-
# Build the full element from the objects
1024-
for obj in objects:
1025-
device.xml_combine(element, [obj.element(), ])
1026-
# Apply the element to the xpath
1027-
device.set_config_changed()
1028-
device.xapi.edit(xpath, ET.tostring(element, encoding='utf-8'), retry_on_peer=cls.HA_SYNC)
1029-
for obj in objects:
1030-
obj._check_child_methods("apply")
1031-
10321043
@classmethod
10331044
def refreshall(cls, parent, running_config=False, add=True,
10341045
exceptions=False, name_only=False):
@@ -1075,11 +1086,9 @@ def refreshall(cls, parent, running_config=False, add=True,
10751086
device = class_instance.nearest_pandevice()
10761087
logger.debug(device.id + ": refreshall called on %s type" % cls)
10771088

1078-
parent_xpath = class_instance._parent_xpath()
1079-
10801089
# Set api_action and xpath
10811090
api_action = device.xapi.show if running_config else device.xapi.get
1082-
xpath = parent_xpath + class_instance.XPATH
1091+
xpath = class_instance.xpath_nosuffix()
10831092
if name_only:
10841093
xpath = xpath + "/entry/@name"
10851094

@@ -1471,8 +1480,6 @@ def _gather_bulk_info(self, func=None):
14711480
logger.debug('{0}: {1} called on {2} object "{3}"'.format(
14721481
dev.id, func, self, self.uid))
14731482
dev.set_config_changed()
1474-
if self.HA_SYNC:
1475-
dev = dev.active()
14761483

14771484
# Determine base xpath to match against.
14781485
xpath = self.xpath_short()
@@ -1608,7 +1615,7 @@ def delete_similar(self):
16081615
self._perform_vsys_dict_import_delete(dev, vsys_dict)
16091616

16101617
# Now perform the bulk delete.
1611-
xpath = self._parent_xpath() + self.XPATH
1618+
xpath = self.xpath_nosuffix()
16121619
if self.SUFFIX == ENTRY:
16131620
entries = ' or '.join(
16141621
"@name='{0}'".format(x.uid) for x in instances)
@@ -1864,6 +1871,9 @@ class VersionedPanObject(PanObject):
18641871
"""
18651872
_UNKNOWN_PANOS_VERSION = (sys.maxsize, 0, 0)
18661873
_DEFAULT_NAME = None
1874+
_TEMPLATE_DEVICE_XPATH = "/config/devices/entry[@name='localhost.localdomain']"
1875+
_TEMPLATE_VSYS_XPATH = _TEMPLATE_DEVICE_XPATH + "/vsys/entry[@name='{vsys}']"
1876+
_TEMPLATE_MGTCONFIG_XPATH = "/config/mgt-config"
18671877

18681878
def __init__(self, *args, **kwargs):
18691879
if self.NAME is not None:
@@ -2234,7 +2244,8 @@ def __setattr__(self, name, value):
22342244
def XPATH(self):
22352245
"""Returns the version specific xpath of this object."""
22362246
panos_version = self.retrieve_panos_version()
2237-
return self._xpaths._get_versioned_value(panos_version, self.parent)
2247+
val = self._xpaths._get_versioned_value(panos_version, self.parent)
2248+
return val.format(vsys=self.vsys or 'vsys1')
22382249

22392250

22402251
class VersionedParamPath(VersioningSupport):
@@ -2479,6 +2490,8 @@ def element(self, elm, settings, comparable=False):
24792490
if token.startswith('entry '):
24802491
junk, var_to_use = token.split()
24812492
child = ET.Element('entry', {'name': settings[var_to_use]})
2493+
elif token == "entry[@name='localhost.localdomain']":
2494+
child = ET.Element('entry', {'name': 'localhost.localdomain'})
24822495
else:
24832496
child = ET.Element(token.format(**settings))
24842497
if child.tag == 'None':
@@ -2670,14 +2683,14 @@ class VsysOperations(VersionedPanObject):
26702683
ALWAYS_IMPORT = False
26712684

26722685
def __init__(self, *args, **kwargs):
2673-
self._xpath_imports = VersioningSupport()
2686+
self._xpath_imports = ParentAwareXpath()
26742687
super(VsysOperations, self).__init__(*args, **kwargs)
26752688

26762689
@property
26772690
def XPATH_IMPORT(self):
26782691
"""Returns the version specific xpath import for this object."""
26792692
panos_version = self.retrieve_panos_version()
2680-
return self._xpath_imports._get_versioned_value(panos_version)
2693+
return self._xpath_imports._get_versioned_value(panos_version, self.parent)
26812694

26822695
def create(self):
26832696
super(VsysOperations, self).create()
@@ -2729,11 +2742,17 @@ def create_import(self, vsys=None):
27292742
device.active().xapi.set(xpath, element, retry_on_peer=True)
27302743

27312744
def xpath_import_base(self, vsys=None):
2732-
if vsys:
2733-
vsys_xpath = self._root_xpath_vsys(vsys)
2734-
else:
2735-
vsys_xpath = self.xpath_vsys()
2736-
return "{0}/import{1}".format(vsys_xpath, self.XPATH_IMPORT)
2745+
template = ''
2746+
p = self
2747+
while p is not None:
2748+
if p.__class__.__name__ == 'Template':
2749+
template = p.xpath()
2750+
break
2751+
p = p.parent
2752+
2753+
vsys_xpath = self._root_xpath_vsys(vsys or self.vsys or 'vsys1')
2754+
return "{0}{1}/import{2}".format(
2755+
template, vsys_xpath, self.XPATH_IMPORT)
27372756

27382757
def delete_import(self, vsys=None):
27392758
"""Delete a vsys import for the object
@@ -2804,8 +2823,7 @@ def refreshall(cls, parent, running_config=False, add=True,
28042823
api_action = device.xapi.show if running_config else device.xapi.get
28052824
if parent.vsys != "shared" and parent.vsys is not None and class_instance.XPATH_IMPORT is not None:
28062825
imports = []
2807-
xpath = '{0}/import{1}'.format(
2808-
parent.xpath_vsys(), class_instance.XPATH_IMPORT)
2826+
xpath = class_instance.xpath_import_base()
28092827
try:
28102828
imports_xml = api_action(xpath, retry_on_peer=True)
28112829
except (err.PanNoSuchNode, pan.xapi.PanXapiError) as e:
@@ -3283,11 +3301,11 @@ def set_config_changed(self, scope=None):
32833301
def _build_xpath(self, root, vsys):
32843302
return self.xpath_root(root, vsys or self.vsys)
32853303

3286-
def xpath_root(self, root_type, vsys):
3304+
def xpath_root(self, root_type, vsys, label='vsys'):
32873305
if root_type == Root.DEVICE:
32883306
xpath = self.xpath_device()
32893307
elif root_type == Root.VSYS:
3290-
xpath = self._root_xpath_vsys(vsys)
3308+
xpath = self._root_xpath_vsys(vsys, label)
32913309
elif root_type == Root.MGTCONFIG:
32923310
xpath = self.xpath_mgtconfig()
32933311
elif root_type == Root.PANORAMA:

0 commit comments

Comments
 (0)