Skip to content

Commit d8ab306

Browse files
Initial release of Bulk Firmware Upgrade Manager
1 parent 2933458 commit d8ab306

1 file changed

Lines changed: 202 additions & 0 deletions

File tree

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import meraki
2+
import datetime
3+
import time
4+
5+
'''
6+
Cisco Meraki Bulk Firmware Upgrade Manager
7+
John M. Kuchta .:|:.:|:. https://github.com/TKIPisalegacycipher
8+
This script will pull network IDs from an org and then create asynchronous action batches. Each batch will contain, for
9+
each network, an action that will delay the upgrade datetime stamp by X days (configurable). Each batch can contain up
10+
to 100 actions, therefore, each batch can modify up to 100 networks.
11+
12+
As always, you should read the docs before diving in. If you know how these features work, then it will be easier to
13+
understand and leverage this tool.
14+
15+
Firmware upgrades endpoint: https://developer.cisco.com/meraki/api-v1/#!get-network-firmware-upgrades
16+
Action batches: https://developer.cisco.com/meraki/api-v1/#!action-batches-overview
17+
18+
NB: Once you start the script, there are no confirmation prompts or previews, so test in a lab if necessary.
19+
20+
NB: When the final batch has been submitted, depending on the batch size, it may take a few minutes to finish. Feeling
21+
creative? Then try extending this script (using existing code, for the most part) to confirm when the batches are
22+
complete. Feeling super creative? Wrap this behind a Flask frontend and have yourself a merry little GUI.
23+
'''
24+
25+
# init Meraki Python SDK session
26+
dashboard = meraki.DashboardAPI(suppress_logging=True, single_request_timeout=120)
27+
28+
29+
# Configurable options
30+
# Organization ID. Replace this with your actual organization ID.
31+
organization_id = 'YOUR ORG ID HERE' # Use your own organization ID.
32+
time_delta_in_days = 30 # Max is 1 month per the firmware upgrades endpoint docs
33+
actions_per_batch = 100 # Max number of actions to submit in a batch. 100 is the maximum. Bigger batches take longer.
34+
wait_factor = 0.33 # Wait factor for action batches when the action batch queue is full.
35+
36+
# Firmware IDs; not needed for rescheduling, only for upgrading. If you plan to use this for upgrading, then you should
37+
# first GET the availableVersions IDs and use those here instead, since they have probably changed from the time this
38+
# was published.
39+
mx_new_firmware_id = 2128 # Did you update this to your actual FW ID by GETing your availableFirmwareVersions?
40+
mx_old_firmware_id = 2009 # Did you update this to your actual FW ID by GETing your availableFirmwareVersions?
41+
42+
43+
def time_formatter(date_time_stamp):
44+
# Basic time formatter to return strings that the API requires
45+
formatted_date_time_stamp = date_time_stamp.replace(microsecond=0).isoformat() + 'Z'
46+
return formatted_date_time_stamp
47+
48+
49+
# Time stamps
50+
utc_now = datetime.datetime.utcnow()
51+
utc_future = utc_now + datetime.timedelta(days=time_delta_in_days)
52+
utc_now_formatted = time_formatter(utc_now)
53+
utc_future_formatted = time_formatter(utc_future)
54+
55+
56+
action_reschedule_existing = {
57+
"products": {
58+
"appliance":
59+
{
60+
"nextUpgrade": {
61+
"time": utc_future_formatted
62+
}
63+
}
64+
}
65+
}
66+
67+
# Use this action to schedule a new upgrade. If you do not provide a time param (as shown above), it will execute
68+
# immediately. IMPORTANT: See API docs for more info before using this.
69+
action_schedule_new_upgrade = {
70+
"products": {
71+
"appliance":
72+
{
73+
"nextUpgrade": {
74+
"time": utc_future_formatted,
75+
"toVersion": {
76+
"id": mx_new_firmware_id
77+
}
78+
}
79+
}
80+
}
81+
}
82+
83+
84+
# GET the network list
85+
networks_list = dashboard.organizations.getOrganizationNetworks(
86+
organizationId=organization_id
87+
)
88+
89+
90+
def format_single_action(resource, operation, body):
91+
# Combine a single set of batch components into an action
92+
action = {
93+
"resource": resource,
94+
"operation": operation,
95+
"body": body
96+
}
97+
98+
return action
99+
100+
101+
def create_single_upgrade_action(network_id):
102+
# Create a single upgrade action
103+
# AB component parts, rename action
104+
action_resource = f'/networks/{network_id}/firmwareUpgrades'
105+
action_operation = 'update'
106+
# Choose whether to reschedule an existing or start a new upgrade
107+
action_body = action_schedule_new_upgrade
108+
109+
upgrade_action = format_single_action(action_resource, action_operation, action_body)
110+
111+
return upgrade_action
112+
113+
114+
def run_an_action_batch(org_id, actions_list, synchronous=False):
115+
# Create and run an action batch
116+
batch_response = dashboard.organizations.createOrganizationActionBatch(
117+
organizationId=org_id,
118+
actions=actions_list,
119+
confirmed=True,
120+
synchronous=synchronous
121+
)
122+
123+
return batch_response
124+
125+
126+
def create_action_list(net_list):
127+
# Creates a list of actions and returns it
128+
# Iterate through the list of network IDs and create an action for each, then collect it
129+
list_of_actions = list()
130+
131+
for network in net_list:
132+
# Create the action
133+
single_action = create_single_upgrade_action(network['id'])
134+
list_of_actions.append(single_action)
135+
136+
return list_of_actions
137+
138+
139+
def batch_actions_splitter(batch_actions):
140+
# Split the list of actions into smaller lists of maximum 100 actions each
141+
# For each ID in range length of network_ids
142+
for i in range(0, len(batch_actions), actions_per_batch):
143+
# Create an index range for network_ids of 100 items:
144+
yield batch_actions[i:i + actions_per_batch]
145+
146+
147+
def action_batch_runner(batch_actions_lists, org_id):
148+
# Create an action batch for each list of actions
149+
# Store the responses
150+
responses = list()
151+
number_of_batches = len(batch_actions_lists)
152+
number_of_batches_submitted = 0
153+
wait_seconds = int(30)
154+
155+
# Make a batch for each list
156+
for batch_action_list in batch_actions_lists:
157+
action_batch_queue_checker(org_id)
158+
batch_response = run_an_action_batch(org_id, batch_action_list)
159+
responses.append(batch_response)
160+
number_of_batches_submitted += 1
161+
162+
# Inform user of progress.
163+
print(f'Submitted batch {number_of_batches_submitted} of {number_of_batches}.')
164+
165+
return responses
166+
167+
168+
def action_batch_queue_checker(org_id):
169+
all_action_batches = dashboard.organizations.getOrganizationActionBatches(organizationId=org_id)
170+
running_action_batches = [batch for batch in all_action_batches if batch['status']['completed'] is False and batch['status']['failed'] is False]
171+
total_running_actions = 0
172+
173+
for batch in running_action_batches:
174+
batch_actions = len(batch['actions'])
175+
total_running_actions += batch_actions
176+
177+
wait_seconds = total_running_actions * wait_factor
178+
179+
while len(running_action_batches) > 4:
180+
print(f'There are already five action batches in progress with a total of {total_running_actions} running actions. Waiting {wait_seconds} seconds.')
181+
time.sleep(wait_seconds)
182+
print('Checking again.')
183+
184+
all_action_batches = dashboard.organizations.getOrganizationActionBatches(organizationId=org_id)
185+
running_action_batches = [batch for batch in all_action_batches if batch['status']['completed'] is False and batch['status']['failed'] is False]
186+
total_running_actions = 0
187+
188+
for batch in running_action_batches:
189+
batch_actions = len(batch['actions'])
190+
total_running_actions += batch_actions
191+
192+
wait_seconds = total_running_actions * wait_factor
193+
194+
195+
# Create a list of upgrade actions
196+
upgrade_actions_list = create_action_list(networks_list)
197+
198+
# Split the list into multiple lists of max 100 items each
199+
upgrade_actions_lists = list(batch_actions_splitter(upgrade_actions_list))
200+
201+
# Run the action batches to clone the networks
202+
upgraded_networks_responses = action_batch_runner(upgrade_actions_lists, organization_id)

0 commit comments

Comments
 (0)