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