diff --git a/bin/panxapi.py b/bin/panxapi.py index dfe42a2..8ef2de4 100755 --- a/bin/panxapi.py +++ b/bin/panxapi.py @@ -209,6 +209,9 @@ def main(): kwargs = { 'cmd': cmd, + 'sync': options['sync'], + 'interval': options['interval'], + 'timeout': options['job_timeout'], } if options['commit_all']: kwargs['action'] = 'all' @@ -238,6 +241,7 @@ def parse_opts(): 'commit': False, 'force': False, 'partial': [], + 'sync': False, 'vsys': [], 'commit_all': False, 'ad_hoc': None, @@ -286,7 +290,7 @@ def parse_opts(): short_options = 'de:gksS:U:C:A:o:l:h:P:K:xpjrXHGDt:T:' long_options = ['version', 'help', - 'ad-hoc=', 'modify', 'force', 'partial=', + 'ad-hoc=', 'modify', 'force', 'partial=', 'sync', 'vsys=', 'src=', 'dst=', 'move=', 'rename', 'clone', 'export=', 'log=', 'recursive', 'cafile=', 'capath=', 'ls', 'serial=', @@ -329,6 +333,8 @@ def parse_opts(): if arg: l = get_parts(arg) [options['partial'].append(s) for s in l] + elif opt == '--sync': + options['sync'] = True elif opt == '--vsys': if arg: l = get_vsys(arg) @@ -637,6 +643,7 @@ def usage(): -C cmd commit candidate configuration --force force commit when conflict --partial part commit specified part + --sync synchronous commit -A cmd commit-all (Panorama) --ad-hoc query perform ad hoc request --modify insert known fields in ad hoc query @@ -663,8 +670,8 @@ def usage(): --nlogs num retrieve num logs --skip num skip num logs --filter filter log selection filter - --interval log job query interval - --timeout log job query timeout + --interval log/commit job query interval + --timeout log/commit job query timeout -K api_key -x print XML response to stdout -p print XML response in Python to stdout diff --git a/doc/pan.xapi.rst b/doc/pan.xapi.rst index ea3a875..e15409c 100644 --- a/doc/pan.xapi.rst +++ b/doc/pan.xapi.rst @@ -281,14 +281,44 @@ user_id(cmd=None, vsys=None) mappings and address objects. **vsys** can be used to target the dynamic update to a specific Virtual System. -commit(cmd=None, action=None) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +commit(cmd=None, action=None, sync=False, interval=None, timeout=None) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The commit() method performs the ``type=commit`` commit configuration API request with the **cmd** argument and optional **action** argument. This schedules a job to execute a configuration mode **commit** command to commit the candidate configuration. + **cmd** is an XML document used to specify commit arguments. + + **action** can be set to "all" to perform a ``commit-all`` on + Panorama. + + Additional arguments include: + + - **sync** + + Perform a synchronous commit when set to *True*. + + The XML API schedules a job to perform the commit operation; the + commit() method will then periodically perform an API request to + determine if the job ID returned in the initial request is complete + and return with the job status. Additional arguments to control + the polling include: + + - **interval** + + A floating point number specifying the query interval in seconds + between each non-finished job status response. + + The default is 0.5 seconds. + + - **timeout** + + The maximum number of seconds to wait for the job to finish. + + The default is to try forever (**timeout** is set to *None* or 0). + op(cmd=None, vsys=None, cmd_xml=False) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/lib/pan/xapi.py b/lib/pan/xapi.py index ae56736..e1bd4a1 100644 --- a/lib/pan/xapi.py +++ b/lib/pan/xapi.py @@ -700,10 +700,29 @@ def user_id(self, cmd=None, vsys=None): if not self.__set_response(response): raise PanXapiError(self.status_detail) - def commit(self, cmd=None, action=None): + def commit(self, cmd=None, action=None, sync=False, + interval=None, timeout=None): self.__set_api_key() self.__clear_response() + if interval is not None: + try: + interval = float(interval) + if interval < 0: + raise ValueError + except ValueError: + raise PanXapiError('Invalid interval: %s' % interval) + else: + interval = _job_query_interval + + if timeout is not None: + try: + timeout = int(timeout) + if timeout < 0: + raise ValueError + except ValueError: + raise PanXapiError('Invalid timeout: %s' % timeout) + query = {} query['type'] = 'commit' query['key'] = self.api_key @@ -719,6 +738,47 @@ def commit(self, cmd=None, action=None): if not self.__set_response(response): raise PanXapiError(self.status_detail) + if sync is not True: + return + + job = self.element_root.find('./result/job') + if job is None: + return + + if self.debug2: + print('commit job:', job.text, file=sys.stderr) + + cmd = 'show jobs id "%s"' % job.text + start_time = time.time() + + while True: + try: + self.op(cmd=cmd, cmd_xml=True) + except PanXapiError as msg: + raise PanXapiError('commit %s: %s' % (cmd, msg)) + + path = './result/job/status' + status = self.element_root.find(path) + if status is None: + raise PanXapiError('no status element in ' + + "'%s' response" % cmd) + if status.text == 'FIN': + # XXX commit vs. commit-all job status + return + + if self.debug2: + print('job %s status %s' % (job.text, status.text), + file=sys.stderr) + + if (timeout is not None and timeout != 0 and + time.time() > start_time + timeout): + raise PanXapiError('timeout waiting for ' + + 'job %s completion' % job.text) + + if self.debug2: + print('sleep %.2f seconds' % interval, file=sys.stderr) + time.sleep(interval) + def op(self, cmd=None, vsys=None, cmd_xml=False): if cmd is not None and cmd_xml: cmd = self.cmd_xml(cmd)