1010import logging
1111import json
1212import requests
13+ import threading
1314
1415# The modules are named different in python2/python3...
1516try :
@@ -27,7 +28,7 @@ class ACI:
2728 # ==============================================================================
2829 # constructor
2930 # ==============================================================================
30- def __init__ (self , apicIp , apicUser , apicPasword ):
31+ def __init__ (self , apicIp , apicUser , apicPasword , refresh = False ):
3132 self .__logger .debug ('Constructor called' )
3233 self .apicIp = apicIp
3334 self .apicUser = apicUser
@@ -36,25 +37,36 @@ def __init__(self, apicIp, apicUser, apicPasword):
3637 self .baseUrl = 'https://' + self .apicIp + '/api/'
3738 self .__logger .debug (f'BaseUrl set to: { self .baseUrl } ' )
3839
40+ self .refresh_auto = refresh
41+ self .refresh_next = None
42+ self .refresh_thread = None
43+ self .refresh_offset = 30
3944 self .session = None
4045 self .token = None
4146
47+ def __refresh_session_timer (self , response ):
48+ self .__logger .debug (f'refreshing the token { self .refresh_offset } s before it expires' )
49+ self .refresh_next = int (response .json ()['imdata' ][0 ]['aaaLogin' ]['attributes' ]['refreshTimeoutSeconds' ])
50+ self .refresh_thread = threading .Timer (self .refresh_next - self .refresh_offset , self .renewCookie )
51+ self .__logger .debug (f'starting thread to refresh token in { self .refresh_next - self .refresh_offset } s' )
52+ self .refresh_thread .start ()
53+
4254 # ==============================================================================
4355 # login
4456 # ==============================================================================
4557 def login (self ) -> bool :
4658 self .__logger .debug ('login called' )
4759
4860 self .session = requests .Session ()
49- self .__logger .info ('Session Object Created' )
61+ self .__logger .debug ('Session Object Created' )
5062
5163 # create credentials structure
5264 userPass = json .dumps ({'aaaUser' : {'attributes' : {'name' : self .apicUser , 'pwd' : self .apicPassword }}})
5365
5466 self .__logger .info (f'Login to apic { self .baseUrl } ' )
5567 response = self .session .post (self .baseUrl + 'aaaLogin.json' , data = userPass , verify = False , timeout = 5 )
5668
57- # Don't rise an exception for 401
69+ # Don't raise an exception for 401
5870 if response .status_code == 401 :
5971 self .__logger .error (f'Login not possible due to Error: { response .text } ' )
6072 self .session = False
@@ -64,15 +76,24 @@ def login(self) -> bool:
6476 response .raise_for_status ()
6577
6678 self .token = response .json ()['imdata' ][0 ]['aaaLogin' ]['attributes' ]['token' ]
67- self .__logger .info ('Successful get Token from APIC' )
79+ self .__logger .debug ('Successful get Token from APIC' )
80+
81+ if self .refresh_auto :
82+ self .__refresh_session_timer (response = response )
6883 return True
6984
7085 # ==============================================================================
7186 # logout
7287 # ==============================================================================
7388 def logout (self ):
74- self .__logger .debug ('Logout from APIC...' )
89+ self .__logger .debug ('logout called' )
90+ self .refresh_auto = False
91+ if self .refresh_thread is not None :
92+ if self .refresh_thread .is_alive ():
93+ self .__logger .debug ('Stoping refresh_auto thread' )
94+ self .refresh_thread .cancel ()
7595 self .postJson (jsonData = {'aaaUser' : {'attributes' : {'name' : self .apicUser }}}, url = 'aaaLogout.json' )
96+ self .__logger .debug ('Logout from APIC sucessfull' )
7697
7798 # ==============================================================================
7899 # renew cookie (aaaRefresh)
@@ -81,11 +102,17 @@ def renewCookie(self) -> bool:
81102 self .__logger .debug ('Renew Cookie called' )
82103 response = self .session .post (self .baseUrl + 'aaaRefresh.json' , verify = False )
83104
84- # Raise Exception for an error 4xx and 5xx
85- response .raise_for_status ()
86-
87- self .token = response .json ()['imdata' ][0 ]['aaaLogin' ]['attributes' ]['token' ]
88- self .__logger .info ('Successful renewed the Token' )
105+ if response .status_code == 200 :
106+ if self .refresh_auto :
107+ self .__refresh_session_timer (response = response )
108+ self .token = response .json ()['imdata' ][0 ]['aaaLogin' ]['attributes' ]['token' ]
109+ self .__logger .debug ('Successfuly renewed the token' )
110+ else :
111+ self .token = False
112+ self .refresh_auto = False
113+ self .__logger .error (f'Could not renew token. { response .text } ' )
114+ response .raise_for_status ()
115+ return False
89116 return True
90117
91118 # ==============================================================================
@@ -108,10 +135,10 @@ def getJson(self, uri, subscription=False) -> {}:
108135
109136 if response .ok :
110137 responseJson = response .json ()
111- self .__logger .info (f'Successful get Data from APIC: { responseJson } ' )
138+ self .__logger .debug (f'Successful get Data from APIC: { responseJson } ' )
112139 if subscription :
113140 subscription_id = responseJson ['subscriptionId' ]
114- self .__logger .info (f'Returning Subscription Id: { subscription_id } ' )
141+ self .__logger .debug (f'Returning Subscription Id: { subscription_id } ' )
115142 return subscription_id
116143 return responseJson ['imdata' ]
117144
@@ -120,7 +147,7 @@ def getJson(self, uri, subscription=False) -> {}:
120147 self .__logger .error (f'Error 400 during get occured: { resp_text } ' )
121148 if resp_text == 'Unable to process the query, result dataset is too big' :
122149 # Dataset was too big, we try to grab all the data with pagination
123- self .__logger .info (f'Trying with Pagination, uri: { uri } ' )
150+ self .__logger .debug (f'Trying with Pagination, uri: { uri } ' )
124151 return self .getJsonPaged (uri )
125152 return resp_text
126153 else :
@@ -148,7 +175,7 @@ def getJsonPaged(self, uri) -> {}:
148175
149176 if response .ok :
150177 responseJson = response .json ()
151- self .__logger .info (f'Successful get Data from APIC: { responseJson } ' )
178+ self .__logger .debug (f'Successful get Data from APIC: { responseJson } ' )
152179 if responseJson ['imdata' ]:
153180 return_data .extend (responseJson ['imdata' ])
154181 else :
@@ -170,7 +197,7 @@ def postJson(self, jsonData, url='mo.json') -> {}:
170197 self .__logger .debug (f'Post Json called data: { jsonData } ' )
171198 response = self .session .post (self .baseUrl + url , verify = False , data = json .dumps (jsonData , sort_keys = True ))
172199 if response .status_code == 200 :
173- self .__logger .info (f'Successful Posted Data to APIC: { response .json ()} ' )
200+ self .__logger .debug (f'Successful Posted Data to APIC: { response .json ()} ' )
174201 return response .status_code
175202 elif response .status_code == 400 :
176203 resp_text = '400: ' + response .json ()['imdata' ][0 ]['error' ]['attributes' ]['text' ]
@@ -192,3 +219,36 @@ def deleteMo(self, dn) -> int:
192219 response .raise_for_status ()
193220
194221 return response .status_code
222+
223+ # ==============================================================================
224+ # snapshot
225+ # ==============================================================================
226+ def snapshot (self , description = "snapshot" ) -> bool :
227+ self .__logger .debug (f'snapshot called { description } ' )
228+
229+ json_payload = [
230+ {
231+ "configExportP" : {
232+ "attributes" : {
233+ "adminSt" : "triggered" ,
234+ "descr" : f"by aciClient - { description } " ,
235+ "dn" : "uni/fabric/configexp-aciclient" ,
236+ "format" : "json" ,
237+ "includeSecureFields" : "yes" ,
238+ "maxSnapshotCount" : "global-limit" ,
239+ "name" : "aciclient" ,
240+ "nameAlias" : "" ,
241+ "snapshot" : "yes" ,
242+ "targetDn" : ""
243+ }
244+ }
245+ }
246+ ]
247+
248+ response = self .postJson (json_payload )
249+ if response == 200 :
250+ self .__logger .debug ('snapshot created and triggered' )
251+ return True
252+ else :
253+ self .__logger .error (f'snapshot creation not succesfull: { response } ' )
254+ return False
0 commit comments