Skip to content

Commit b24f30f

Browse files
Merge pull request #20 from dariomi/master
Add subscription functions
2 parents 5072b04 + 7e979af commit b24f30f

5 files changed

Lines changed: 135 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
> * v1.00, 11.2020 -- Adjustments for OpenSource - Andreas Graber
44
> * v1.3, 06.2021 -- Snapshot creation added - Dario Kaelin
55
> * v1.5, 01.2023 -- Dependency Update
6+
> * v1.6, 06.2024 -- Improvement for subscription - Dario Kaelin

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,17 @@ You can specify a tenant in variable ```target_dn``` or not provide any to do a
102102
aci.snapshot(description='test', target_dn='/uni/tn-test')
103103
```
104104

105+
### Subscriptions
106+
You can subscribe to an ACI object with websocket and get near-instant updates on-change.
107+
To use the subscriptions you have to:
108+
- Login to ACI
109+
- Open websocket to ACI
110+
- Subscribe to an ACI object via aciClient.subscribe
111+
- Refresh subscription to an ACI object via aciClient.subscription_refresh
112+
- Handle messages sent from ACI through websocket
113+
114+
You can find example code here: examples/subscription.py
115+
105116
## Testing
106117

107118
```

aciClient/aci.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,56 @@ def snapshot(self, description="snapshot", target_dn="") -> bool:
252252
else:
253253
self.__logger.error(f'snapshot creation not succesfull: {response}')
254254
return False
255+
256+
# ==============================================================================
257+
# subscribe
258+
# ==============================================================================
259+
def subscribe(
260+
self, subscription_dn: str, timeout: int = 60, query_parameters: list = []
261+
) -> {}:
262+
query_parameters.append("subscription=yes")
263+
query_parameters.append(f"refresh-timeout={timeout}")
264+
265+
endpoint = f"{self.baseUrl}{str(subscription_dn)}?{'&'.join(query_parameters)}"
266+
self.__logger.debug(f"Subscribe to: {endpoint}")
267+
268+
response = self.session.get(endpoint, verify=False)
269+
if response.status_code == 200:
270+
self.__logger.debug(f"Successful subscribed to APIC: {response.json()}")
271+
return response.json()
272+
elif response.status_code == 400:
273+
resp_text = (
274+
f"400: {response.json()['imdata'][0]['error']['attributes']['text']}"
275+
)
276+
self.__logger.error(f"Error 400 during get occured: {resp_text}")
277+
return response.json()
278+
else:
279+
self.__logger.error(f"Error during get occured: {response.json()}")
280+
response.raise_for_status()
281+
return response.json()
282+
283+
# ==============================================================================
284+
# subscription_refresh
285+
# ==============================================================================
286+
def subscription_refresh(self, subscription_id: str) -> {}:
287+
query_parameters = [f"id={subscription_id}"]
288+
289+
endpoint = (
290+
f"{self.baseUrl}/subscriptionRefresh.json?{'&'.join(query_parameters)}"
291+
)
292+
self.__logger.debug(f"Refresh subscription: {subscription_id}")
293+
294+
response = self.session.post(endpoint, verify=False)
295+
if response.status_code == 200:
296+
self.__logger.debug(f"Successful subscribed to APIC: {response.json()}")
297+
return response.json()
298+
elif response.status_code == 400:
299+
resp_text = (
300+
f"400: {response.json()['imdata'][0]['error']['attributes']['text']}"
301+
)
302+
self.__logger.error(f"Error 400 during get occured: {resp_text}")
303+
return response.json()
304+
else:
305+
self.__logger.error(f"Error during get occured: {response.json()}")
306+
response.raise_for_status()
307+
return response.json()

examples/subscription.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import json
2+
import time
3+
import ssl
4+
from threading import Thread
5+
6+
from aciClient import ACI
7+
import websocket
8+
9+
10+
def on_message(ws, message):
11+
print("Websocket received a message")
12+
# Message contains the data transmited from ACI in plaintext
13+
print(json.loads(message)["imdata"])
14+
15+
16+
def on_close(ws, close_status_code, close_msg):
17+
print("Websocket was closed")
18+
19+
20+
def on_open(ws):
21+
print("Websocket was opened")
22+
23+
24+
def open_websocket(ip: str, token: str):
25+
ws = websocket.WebSocketApp(
26+
f"wss://{ip}/socket{token}",
27+
on_message=on_message,
28+
on_open=on_open,
29+
on_close=on_close,
30+
)
31+
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
32+
33+
34+
def main():
35+
hostname = "sandboxapicdc.cisco.com"
36+
username = "admin"
37+
password = "!v3G@!4@Y"
38+
39+
aci = ACI(hostname, username, password)
40+
41+
# Login to ACI
42+
aci.login()
43+
44+
# Open websocket to ACI with the login token
45+
46+
Thread(target=open_websocket, args=(aci.apicIp, aci.token)).start()
47+
time.sleep(5)
48+
49+
# Subscribe to an ACI object
50+
subscription_dn = "node/class/faultInst.json"
51+
timeout = 60 # Optional. Default value in ACI
52+
query_parameters = ["order-by=faultInst.lastTransition|asc"] # Optional
53+
54+
response = aci.subscribe(
55+
subscription_dn=subscription_dn,
56+
timeout=timeout,
57+
query_parameters=query_parameters,
58+
)
59+
subscription_id = response["subscriptionId"]
60+
61+
while True:
62+
time.sleep(30)
63+
print("Refreshing subscription")
64+
aci.subscription_refresh(subscription_id=subscription_id)
65+
66+
67+
if __name__ == "__main__":
68+
main()

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
long_description = f.read()
88

99
setup(name='aciClient',
10-
version='1.5',
10+
version='1.6',
1111
description='aci communication helper class',
1212
url='http://www.netcloud.ch',
13-
author='mze',
13+
author='Netcloud AG',
1414
author_email='nc_dev@netcloud.ch',
1515
license='MIT',
1616
packages=['aciClient'],

0 commit comments

Comments
 (0)