1 import datetime
2 import time
3 import logging
4
5 import requests
6
7
8 class PaloAltoFirewallAPI(object):
9 error_codes = {
10 '400': 'Bad request: A required parameter is missing, an illegal parameter value is used.',
11 '403': ('Forbidden: '
12 'Authentication or authorization errors including invalid key or insufficient admin access rights. '
13 'Learn how to Get Your API Key.'
14 ),
15 '1': 'Unknown command: The specific config or operational command is not recognized.',
16 '2': 'Unknown command: The specific config or operational command is not recognized.',
17 '3': 'Unknown command: The specific config or operational command is not recognized.',
18 '4': 'Unknown command: The specific config or operational command is not recognized.',
19 '5': 'Unknown command: The specific config or operational command is not recognized.',
20 '6': ('Bad Xpath: '
21 'The xpath specified in one or more attributes of the command is invalid. '
22 'Check the API browser for proper xpath values.'
23 ),
24 '7': ('Object not present: '
25 'Object specified by the xpath is not present. '
26 'For example, entry[@name="value"] where no object with name "value" is present.'
27 ),
28 '8': 'Object not unique: For commands that operate on a single object, the specified object is not unique.',
29 '10': ('Reference count not zero: '
30 'Object cannot be deleted as there are other objects that refer to it. '
31 'For example, address object still in use in policy.'
32 ),
33 '11': 'Internal error: Check with technical support when seeing these errors.',
34 '12': 'Invalid object: Xpath or element values provided are not complete.',
35 '14': ('Operation not possible: '
36 'Operation is allowed but not possible in this case. '
37 'For example, moving a rule up one position when it is already at the top.'
38 ),
39 '15': ('Operation denied: '
40 'Operation is allowed. For example, Admin not allowed to delete own account, '
41 'Running a command that is not allowed on a passive device.'
42 ),
43 '16': 'Unauthorized: The API role does not have access rights to run this query.',
44 '17': 'Invalid command: Invalid command or parameters.',
45 '18': 'Malformed command: The XML is malformed.',
46 '19': 'Success: Command completed successfully.',
47 '20': 'Success: Check with technical support when seeing these errors.',
48 '21': 'Internal error: ',
49 '22': 'Session timed out: The session for this query timed out.',
50 }
51
52 def __init__(self, ip, username='', password='', api_key=''):
53 """
54 :param ip:
55 :param username:
56 :param password:
57 :param api_key: if exist, ignore username and password
58 """
59 self.ip = ip
60 if api_key:
61 self.key = api_key
62 else:
63 key_url = 'https://{ip}/api/?type=keygen&user={username}&password={password}'.format(
64 ip=ip, username=username, password=password
65 )
66 self.key = xmltodict.parse(
67 requests.post(key_url, verify=False).content
68 ).get('response').get('result').get('key')
69 self.url = 'https://{ip}/api'.format(ip=ip)
70
71 def _request(self, params, method='post'):
72 """
73 :param params:
74 {
75 'type': 'config',
76 'action': 'get',
77 'key': self.key,
78 'xpath': xpath,
79 }
80 :return:
81 {
82 'code': '19',
83 'msg': 'Success: Command completed successfully.',
84 'success': True,
85 'result': Dict,
86 }
87 """
88 response = xmltodict.parse(
89 requests.request(
90 method=method, url=self.url, params=params, verify=False
91 ).text
92 ).get('response')
93 code = response.get('@code')
94 if code in ('19', '20', None):
95 msg = response.get('msg')
96 else:
97 msg = self.error_codes.get(code)
98 result = {
99 'code': code,
100 'msg': msg,
101 'success': False,
102 'result': None,
103 }
104 if response.get('@status') == 'success':
105 result['success'] = True
106 result['result'] = response.get('result')
107 else:
108 result['success'] = False
109 return result
110
111 def get_report(self, report_name, period='last-hour', top_n=5, wait_for_result=30):
112 """
113 :param report_name:
114 acc-summary
115 custom-dynamic-report
116 top-app-summary
117 top-application-categories-summary
118 top-application-risk-summary
119 top-application-subcategories-summary
120 top-application-tech-summary
121 top-applications-summary
122 top-applications-trsum
123 top-attacker-countries-summary
124 top-attackers-summary
125 top-attacks-acc
126 top-blocked-url-categories-summary
127 top-blocked-url-summary
128 top-blocked-url-user-behavior-summary
129 top-data-dst-countries-summary
130 top-data-dst-summary
131 top-data-egress-zones-summary
132 top-data-filename-summary
133 top-data-filetype-summary
134 top-data-ingress-zones-summary
135 top-data-src-countries-summary
136 top-data-src-summary
137 top-data-type-summary
138 top-dst-countries-summary
139 top-dst-summary
140 top-egress-zones-summary
141 top-hip-objects-details
142 top-hip-objects-summary
143 top-hip-profiles-details
144 top-hip-profiles-summary
145 top-hip-report-links
146 top-hr-applications-summary
147 top-ingress-zones-summary
148 top-rule-summary
149 top-spyware-phonehome-summary
150 top-spyware-threats-summary
151 top-src-countries-summary
152 top-src-summary
153 top-threat-egress-zones-summary
154 top-threat-ingress-zones-summary
155 top-threats-type-summary
156 top-url-categories-summary
157 top-url-summary
158 top-url-user-behavior-summary
159 top-victim-countries-summary
160 top-victims-summary
161 top-viruses-summary
162 top-vulnerabilities-summary
163 :param period:
164 last-60-seconds
165 last-15-minutes
166 last-hour
167 last-12-hrs
168 last-7-days,
169 last-calendar-day
170 :param top_n:
171 :param wait_for_result: Return report-result within 30 seconds, otherwise return job id
172 :return: Dict
173 """
174 params = {
175 'reportname': report_name,
176 'type': 'report',
177 'reporttype': 'dynamic',
178 'period': period,
179 'topn': top_n,
180 'key': self.key,
181 }
182
183 res = self._request(params=params)
184 result = {
185 'job_id': None,
186 'report': {},
187 'success': True
188 }
189 data = res.get('result') or {}
190 if not res.get('success'):
191 result['success'] = False
192 result['msg'] = data.get('msg')
193 return result
194 else:
195 result['job_id'] = job_id = data.get('job')
196 if wait_for_result:
197 report = {}
198 while not report and wait_for_result > 0:
199 time.sleep(1)
200 wait_for_result -= 1
201 res = self.get_job(job_id, job_type='report')
202 if res.get('success'):
203 report = res.get('result')
204 result['success'] = True
205 else:
206 result['success'] = False
207
208 result.update(**report)
209 if not result['report']:
210 result['report'] = {}
211 return result
212
213 def get_job(self, job_id, job_type='report'):
214 """
215 :param job_id: def get_report:
216 :param job_type: default = report
217 :return:
218 """
219 params = {
220 'action': 'get',
221 'type': job_type,
222 'job-id': job_id,
223 'key': self.key,
224 }
225 return self._request(params=params)
226
227 def _sp_config(self, _type, rel_path, entry_name='', action='get', **kwargs):
228 """
229 :param _type:
230 :param rel_path:
231 :param entry_name:
232 :param action:
233 :param kwargs: extra params
234 :return:
235 """
236 limit_types = ('deviceconfig', 'network', 'platform', 'vsys')
237 if _type not in limit_types:
238 raise Exception('"_type" must be one of {limit_types}'.format(limit_types=limit_types))
239 rel_path = rel_path.strip('/')
240 if entry_name and not isinstance(entry_name, (list, tuple)):
241 entry_name = [entry_name]
242 if _type in ('deviceconfig', 'network', 'platform'):
243 rel_path = os.path.join(_type, rel_path).replace('\\', '/')
244 if entry_name:
245 entries = ' or '.join(["@name='{name}'".format(name=name) for name in entry_name])
246 xpath = (
247 "/config/devices/entry[@name='localhost.localdomain']/{rel_path}/entry[{entries}]"
248 ).format(rel_path=rel_path, entries=entries)
249 else:
250 xpath = "/config/devices/entry[@name='localhost.localdomain']/{rel_path}".format(rel_path=rel_path)
251 else: # 'vsys':
252 if entry_name:
253 entries = ' or '.join(["@name='{name}'".format(name=name) for name in entry_name])
254 xpath = (
255 "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/{rel_path}/"
256 "entry[{entries}]"
257 ).format(rel_path=rel_path, entries=entries)
258 else:
259 xpath = (
260 "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/{rel_path}"
261 ).format(rel_path=rel_path)
262 params = {
263 'type': 'config',
264 'action': action,
265 'key': self.key,
266 'xpath': xpath,
267 }
268 params.update(**kwargs)
269 return self._request(params=params)
270
271 def set_config(self, _type, rel_path, element='', action='set'):
272 """
273 :param _type:
274 deviceconfig
275 network
276 platform
277 vsys
278 :param rel_path: 'interface/ethernet'
279 :param element: <xml>
280 <entry name="ethernet1/1">
281 <layer3>
282 <lldp>
283 <profile>lenovo_lldp</profile>
284 <enable>yes</enable>
285 </lldp>
286 </layer3>
287 </entry>
288 :param action: Should be one of : [
289 clone, complete, delete, edit, get, multi-clone, multi-move, override, rename, set, show
290 ]
291 :return: {
292 'code': '19',
293 'msg': 'Success: Command completed successfully.',
294 'success': True,
295 'result': Dict,
296 }
297 Usage:
298 >>> pa = PaloAltoFirewallAPI(api_key='x')
299 >>> element =
300 <entry name="ethernet1/1">
301 <layer3><lldp><enable>yes</enable><profile>lenovo_lldp</profile></lldp></layer3>
302 </entry>
303 >>> pa.set_config(_type='network', rel_path='interface/ethernet', element=element)
304 >>>
305 >>> element =
306 <entry name="SDWAN-Vedge-02-Public-IP">
307 <ip-netmask>192.168.1.2</ip-netmask>
308 <description>SDWAN-Vedge-02-Public-IP</description>
309 </entry>
310 >>> pa.set_config(_type='vsys', rel_path='address', element=element)
311 >>>
312 >>> element =
313 <entry name="SDWAN-Vedge-02-NAT">
314 <to>
315 <member>Internet_zone</member>
316 </to>
317 <from>
318 <member>Office_zone</member>
319 </from>
320 <source>
321 <member>SDWAN-Vedge-02-Int-Port-IP</member>
322 </source>
323 <destination>
324 <member>any</member>
325 </destination>
326 <service>any</service>
327 <description>SDWAN-Ser2-vEdge</description>
328 <to-interface>ethernet1/2</to-interface>
329 <disabled>no</disabled>
330 <source-translation>
331 <static-ip>
332 <bi-directional>yes</bi-directional>
333 <translated-address>SDWAN-Vedge-02-Public-IP</translated-address>
334 </static-ip>
335 </source-translation>
336 </entry>
337 >>> pa.set_config(_type='vsys', rel_path='rulebase/nat/rules', element=element)
338 """
339 limit_types = ('deviceconfig', 'network', 'platform', 'vsys')
340 if _type not in limit_types:
341 raise Exception('"_type" must be one of {limit_types}'.format(limit_types=limit_types))
342 rel_path = rel_path.strip('/')
343
344 if _type in ('deviceconfig', 'network', 'platform'):
345 rel_path = os.path.join(_type, rel_path).replace('\\', '/')
346 xpath = "/config/devices/entry[@name='localhost.localdomain']/{rel_path}".format(
347 rel_path=rel_path
348 )
349 else: # 'vsys':
350 xpath = (
351 "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/{rel_path}"
352 ).format(rel_path=rel_path)
353 params = {
354 'type': 'config',
355 'action': action,
356 'key': self.key,
357 'xpath': xpath,
358 'element': element
359 }
360 return self._request(params=params)
361
362 def get_config(self, _type, rel_path, entry_name=''):
363 """
364 :param _type:
365 deviceconfig
366 network
367 platform
368 vsys
369 :param rel_path: 'interface/ethernet'
370 :param entry_name: 'ethernet1/1'
371 :return: {
372 'code': '19',
373 'msg': 'Success: Command completed successfully.',
374 'success': True,
375 'result': Dict,
376 }
377 Usage:
378 >>> pa = PaloAltoFirewallAPI(api_key='x')
379 >>> pa.get_config(_type='network', rel_path='interface/ethernet', entry_name='ethernet name')
380 >>> pa.get_config(_type='vsys', rel_path='address', entry_name='address name')
381 >>> pa.get_config(_type='vsys', rel_path='rulebase/nat/rules', entry_name='rule name')
382 """
383 return self._sp_config(_type=_type, rel_path=rel_path, entry_name=entry_name, action='get')
384
385 def get_entry_members(self, _type, rel_path, entry_name, member_names=None):
386 """
387 :param _type:
388 deviceconfig
389 network
390 platform
391 vsys
392 :param rel_path: 'interface/ethernet'
393 :param entry_name: 'CT-Address-Group'
394 :param member_names: ['ethernet1/1', ]
395 :return: {
396 'code': '19',
397 'msg': 'Success: Command completed successfully.',
398 'success': True,
399 'result': Dict,
400 }
401 Usage:
402 >>> pa = PaloAltoFirewallAPI(api_key='x')
403 >>> pa.get_entry_members(_type='vsys', rel_path='address-group', entry_name='CT-Address-Group', member_names=['CT-Address-0060', 'CT-Address-0061'])
404 """
405 limit_types = ('deviceconfig', 'network', 'platform', 'vsys')
406 if _type not in limit_types:
407 raise Exception('"_type" must be one of {limit_types}'.format(limit_types=limit_types))
408 rel_path = rel_path.strip('/')
409
410 if _type in ('deviceconfig', 'network', 'platform'):
411 rel_path = os.path.join(_type, rel_path).replace('\\', '/')
412 if member_names:
413 members = ' or '.join(["text()='{name}'".format(name=name) for name in member_names])
414 xpath = (
415 "/config/devices/entry[@name='localhost.localdomain']/{rel_path}/entry[@name='{entry_name}']/"
416 "static/member[{members}]"
417 ).format(rel_path=rel_path, entry_name=entry_name, members=members)
418 else:
419 xpath = (
420 "/config/devices/entry[@name='localhost.localdomain']/{rel_path}/entry[@name='{entry_name}']"
421 ).format(rel_path=rel_path, entry_name=entry_name)
422 else: # 'vsys':
423 if member_names:
424 members = ' or '.join(["text()='{name}'".format(name=name) for name in member_names])
425 xpath = (
426 "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/{rel_path}/"
427 "entry[@name='{entry_name}']/static/member[{members}]"
428 ).format(rel_path=rel_path, entry_name=entry_name, members=members)
429 else:
430 xpath = (
431 "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/{rel_path}/"
432 "entry[@name='{entry_name}']/static/member"
433 ).format(rel_path=rel_path, entry_name=entry_name)
434 params = {
435 'type': 'config',
436 'action': 'get',
437 'key': self.key,
438 'xpath': xpath,
439 }
440 return self._request(params=params)
441
442 def delete_entry_members(self, _type, rel_path, entry_name, member_names=None):
443 """
444 :param _type:
445 deviceconfig
446 network
447 platform
448 vsys
449 :param rel_path: 'address-group'
450 :param entry_name: 'CT-Address-Group'
451 :param member_names: ['ethernet1/1', ]
452 :return: {
453 'code': '19',
454 'msg': 'Success: Command completed successfully.',
455 'success': True,
456 'result': Dict,
457 }
458 Usage:
459 >>> pa = PaloAltoFirewallAPI(api_key='x')
460 >>> pa.get_config(
461 >>> _type='vsys', rel_path='address-group', entry_name='CT-Address-Group',
462 >>> member_names=['CT-Address-0060', 'CT-Address-0061']
463 >>> )
464 >>>
465 """
466 limit_types = ('deviceconfig', 'network', 'platform', 'vsys')
467 if _type not in limit_types:
468 raise Exception('"_type" must be one of {limit_types}'.format(limit_types=limit_types))
469 rel_path = rel_path.strip('/')
470
471 if _type in ('deviceconfig', 'network', 'platform'):
472 rel_path = os.path.join(_type, rel_path).replace('\\', '/')
473 if member_names:
474 members = ' or '.join(["text()='{name}'".format(name=name) for name in member_names])
475 xpath = (
476 "/config/devices/entry[@name='localhost.localdomain']/{rel_path}/entry[@name='{entry_name}']/"
477 "static/member[{members}]"
478 ).format(rel_path=rel_path, entry_name=entry_name, members=members)
479 else:
480 xpath = (
481 "/config/devices/entry[@name='localhost.localdomain']/{rel_path}/entry[@name='{entry_name}']"
482 ).format(rel_path=rel_path, entry_name=entry_name)
483 else: # 'vsys':
484 if member_names:
485 members = ' or '.join(["text()='{name}'".format(name=name) for name in member_names])
486 xpath = (
487 "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/{rel_path}/"
488 "entry[@name='{entry_name}']/static/member[{members}]"
489 ).format(rel_path=rel_path, entry_name=entry_name, members=members)
490 else:
491 xpath = (
492 "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/{rel_path}/"
493 "entry[@name='{entry_name}']/static/member"
494 ).format(rel_path=rel_path, entry_name=entry_name)
495 params = {
496 'type': 'config',
497 'action': 'delete',
498 'key': self.key,
499 'xpath': xpath,
500 }
501 return self._request(params=params)
502
503 def delete_config(self, _type, rel_path, entry_name):
504 """
505 :param _type:
506 deviceconfig
507 network
508 platform
509 vsys
510 :param rel_path: 'interface/ethernet'
511 :param entry_name: 'ethernet1/1' or ['ethernet1/1', 'ethernet1/2']
512 :return: {
513 'code': '19',
514 'msg': 'Success: Command completed successfully.',
515 'success': True,
516 'result': Dict,
517 }
518 # Usage:
519 >>> pa = PaloAltoFirewallAPI(api_key='x')
520 >>> pa.delete_config(_type='network', rel_path='interface/ethernet', entry_name='ethernet name')
521 >>> pa.delete_config(_type='vsys', rel_path='service', entry_name='service name')
522 >>> pa.delete_config(_type='vsys', rel_path='address', entry_name='address name')
523 >>> pa.delete_config(_type='vsys', rel_path='address-group', entry_name='address-group name')
524 >>> pa.delete_config(_type='vsys', rel_path='rulebase/nat/rules', entry_name='rule name')
525 """
526 return self._sp_config(_type=_type, rel_path=rel_path, entry_name=entry_name, action='delete')
527
528 def move_config_position(self, rel_path, entry_name, where='top', _type='vsys'):
529 """
530 :param rel_path:
531 :param entry_name:
532 :param where:
533 :param _type:
534 :return:
535 """
536 return self._sp_config(
537 _type=_type, rel_path=rel_path, entry_name=entry_name, action='move', where=where
538 )
539
540 def is_backup(self):
541 cmd = '<show><high-availability><state></state></high-availability></show>'
542 params = {
543 'type': 'op',
544 'key': self.key,
545 'cmd': cmd,
546 }
547 try:
548 res = requests.post(url=self.url, params=params, verify=False)
549 state = xmltodict.parse(res.content).get('response').get('result').get('group').get('local-info').get(
550 'state')
551 if state == 'passive':
552 return True
553 except:
554 pass
555 return False
556
557 def revert(self, command='<revert><config></config></revert>'):
558 """
559 :param command: <revert><config></config></revert>
560 :return:
561 """
562 return self.operational_commands(command=command)
563
564 def commit(self, command='<commit></commit>'):
565 """
566 :param command: '<commit></commit>'
567 :return: str
568 """
569 params = {
570 'type': 'commit',
571 'key': self.key,
572 'cmd': command
573 }
574 return self._request(params=params)
575
576 def acquire_lock(self, comment):
577 command = '''
578 <request>
579 <config-lock>
580 <add><comment>{comment}</comment></add>
581 </config-lock>
582 </request>
583 '''.format(comment=comment)
584 return self.operational_commands(command)
585
586 def release_lock(self, command='<request><config-lock><remove></remove></config-lock></request>'):
587 return self.operational_commands(command)
588
589 def operational_commands(self, command):
590 """
591 :param command: '<show><high-availability><state></state></high-availability></show>'
592 :return:
593 """
594 params = {
595 'type': 'op',
596 'key': self.key,
597 'cmd': command,
598 }
599 return self._request(params=params)
600
601 def export_configuration(self):
602 """
603 :return: xml
604 """
605 params = {
606 'type': 'export',
607 'key': self.key,
608 'category': 'configuration'
609 }
610 result = {
611 'success': False,
612 }
613 try:
614 xml = requests.get(url=self.url, params=params, verify=False).text
615 result.update(success=True, result=xml)
616 except Exception as e:
617 result.update(success=False, error=str(e))
618 return result
619
620 def show_job_status(self, job_id):
621 command = '<show><jobs><id>{}</id></jobs></show>'.format(job_id)
622 return self.operational_commands(command)
623
624
625 def now_time():
626 return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
627
628
629 class PAChangeBase(object):
630 def __init__(self, ip, api_key, lock_comment='PYTHON automation deployment lock(object updates)'):
631 self.ip = ip
632 self.api_key = api_key
633 self._pa = PaloAltoFirewallAPI(ip, api_key=api_key)
634 self.lock_comment = lock_comment
635
636 def has_pending_changes(self):
637 command = '<check><pending-changes></pending-changes></check>'
638 res = self._pa.operational_commands(command)
639 if not res.get('success') or res.get('result') != 'no':
640 return True
641 return False
642
643 def has_lock(self):
644 command = '<show><config-locks></config-locks></show>'
645 res = self._pa.operational_commands(command)
646 if not res.get('success'):
647 return False
648 result = res.get('result')
649 locks = result.get('config-locks')
650 if not locks:
651 return False
652
653 # 如果锁是本程序的锁, 则释放锁
654 if locks.get('entry').get('comment') == self.lock_comment:
655 self.release_lock()
656 return False
657 else:
658 return True
659
660 def acquire_lock(self):
661 res = self._pa.acquire_lock(comment=self.lock_comment)
662 if not res.get('success'):
663 return False
664 if 'Successfully acquired lock' in res.get('result'):
665 return True
666 return False
667
668 def release_lock(self):
669 res = self._pa.release_lock()
670 if not res.get('success'):
671 return False
672 if 'Config lock released' in res.get('result'):
673 return True
674 return False
675
676 def commit(self, callback_res):
677 res = self._pa.commit()
678 if not res.get('success'):
679 return False
680 job_id = res.get('result').get('job')
681 while True:
682 res = self._pa.show_job_status(job_id=job_id)
683 if not res.get('success'):
684 return False
685 job = res.get('result').get('job')
686 job_details = job.get('details')
687 if not job_details:
688 time.sleep(1)
689 continue
690 if 'Commit failed' in str(job_details):
691 for line in job_details.get('line'):
692 callback_res['msg'].append('{} {}'.format(now_time(), line))
693 return False
694 return True
695 return True
696
697 def revert(self):
698 res = self._pa.revert()
699 if not res.get('success'):
700 return False
701 return True
702
703 def do_change(self, **kwargs):
704 raise Exception('Must be overwrite this function')
705
706 def run(self, **kwargs):
707 result = {'success': True, 'ip': self.ip, 'msg': []}
708 try:
709 # 如果有pending changes, 则直接返回
710 if self.has_pending_changes():
711 result['success'] = False
712 result['msg'].append('{} There are pending changes'.format(now_time()))
713 return result
714 result['msg'].append('{} There are no pending changes'.format(now_time()))
715
716 if self.has_lock():
717 result['success'] = False
718 result['msg'].append('{} Config lock was not released'.format(now_time()))
719 return result
720
721 if not self.acquire_lock():
722 result['success'] = False
723 result['msg'].append('{} Acquire config lock failed'.format(now_time()))
724 return result
725 result['msg'].append('{} Acquire config lock successful'.format(now_time()))
726 try:
727 self.do_change(result=result, **kwargs)
728 if self.commit(result):
729 result['msg'].append('{} Commit successful'.format(now_time()))
730 else:
731 result['success'] = False
732 result['msg'].append('{} Commit failed'.format(now_time()))
733 except Exception as e:
734 logging.error(e)
735 result['success'] = False
736 result['msg'].append('{} {}'.format(now_time(), str(e)))
737 finally:
738 self.release_lock()
739 result['msg'].append('{} Release config lock successful'.format(now_time()))
740 except Exception as e:
741 result['success'] = False
742 result['msg'].append('{} {}'.format(now_time(), str(e)))
743 return result
744
745
746 def test():
747 # get top app
748 result = {
749 'top_app_src': [],
750 'top_app_dst': [],
751 }
752 try:
753 ip = '1.1.1.1'
754 username = 'username'
755 password = 'password'
756 api_key = 'api_key'
757 # PaloAltoFirewallAPI(ip=ip, username=username, password=password)
758 pa = PaloAltoFirewallAPI(ip=ip, api_key='key')
759 for direction in ['src', 'dst']:
760 res = pa.get_report(
761 report_name='top-%s-summary' % direction, period='last-hour', top_n=10,
762 wait_for_result=timeout
763 )
764 if res.get('success'):
765 entries = res.get('report', {}).get('entry', [])
766 if not entries:
767 continue
768 temp = []
769 user_key = '%ssuer' % direction
770 for entry in entries:
771 user = entry.get(user_key) or 'unknown'
772 volume_bytes = int(entry.get('bytes'))
773 volume_mb = volume_bytes / 1000.0 / 1000.0
774 temp.append({
775 'Name': user,
776 'volume': '%2s MB' % round(volume_mb, 2),
777 'traffic': '%2s %%' % round(
778 volume_mb * 8 / link_bandwidth / (60 * 60 * 24) * 100, 2
779 )
780 })
781 result['top_app_%s' % direction] = temp
782 except Exception as e:
783 logging.error(e)
784
785
786 def test2():
787 class PBRObjectChanger(PAChangeBase):
788 def do_change(self, **kwargs):
789 # Business code
790 pass
791
792
793 if __name__ == '__main__':
794 test()
795 test2()