1
1
from collections import Counter
2
+ import copy
3
+ import time
4
+ import http
5
+ import http .client
2
6
import pynetbox
3
7
import requests
4
8
import os
@@ -17,12 +21,14 @@ def __init__(self, settings):
17
21
module_added = 0 ,
18
22
module_port_added = 0 ,
19
23
images = 0 ,
24
+ connection_errors = 0 ,
20
25
)
21
26
self .url = settings .NETBOX_URL
22
27
self .token = settings .NETBOX_TOKEN
23
28
self .handle = settings .handle
24
29
self .netbox = None
25
30
self .ignore_ssl = settings .IGNORE_SSL_ERRORS
31
+ self .retry_delay = int (settings .RETRY_DELAY )
26
32
self .modules = False
27
33
self .connect_api ()
28
34
self .verify_compatibility ()
@@ -80,65 +86,91 @@ def create_manufacturers(self, vendors):
80
86
self .handle .verbose_log (f"Error during manufacturer creation. - { request_error .error } " )
81
87
82
88
def create_device_types (self , device_types_to_add ):
83
- for device_type in device_types_to_add :
84
-
85
- # Remove file base path
86
- src_file = device_type ["src" ]
87
- del device_type ["src" ]
88
-
89
- # Pre-process front/rear_image flag, remove it if present
90
- saved_images = {}
91
- image_base = os .path .dirname (src_file ).replace ("device-types" ,"elevation-images" )
92
- for i in ["front_image" ,"rear_image" ]:
93
- if i in device_type :
94
- if device_type [i ]:
95
- image_glob = f"{ image_base } /{ device_type ['slug' ]} .{ i .split ('_' )[0 ]} .*"
96
- images = glob .glob (image_glob , recursive = False )
97
- if images :
98
- saved_images [i ] = images [0 ]
99
- else :
100
- self .handle .log (f"Error locating image file using '{ image_glob } '" )
101
- del device_type [i ]
89
+ retry_amount = 2
90
+
91
+ # Treat the original data as immutable in case we encounter a connection error.
92
+ for device_type_immutable in device_types_to_add :
93
+ # In the event we hit a ConnectionReset error on this item, we want to retry it.
94
+ # If it fails twice, assume it's an issue with the device_type
95
+ retries = 0
96
+
97
+ while retries < retry_amount :
98
+ device_type = copy .deepcopy (device_type_immutable ) # Can this be a copy.copy(device_type_immutable)?
102
99
103
- try :
104
- dt = self .device_types .existing_device_types [device_type ["model" ]]
105
- self .handle .verbose_log (f'Device Type Exists: { dt .manufacturer .name } - '
106
- + f'{ dt .model } - { dt .id } ' )
107
- except KeyError :
108
100
try :
109
- dt = self .netbox .dcim .device_types .create (device_type )
110
- self .counter .update ({'added' : 1 })
111
- self .handle .verbose_log (f'Device Type Created: { dt .manufacturer .name } - '
112
- + f'{ dt .model } - { dt .id } ' )
113
- except pynetbox .RequestError as e :
114
- self .handle .log (f'Error { e .error } creating device type:'
115
- f' { device_type ["manufacturer" ]["name" ]} { device_type ["model" ]} ' )
101
+ if retries == 0 :
102
+ self .handle .verbose_log (f'Processing Source File: { device_type ["src" ]} ' )
103
+ else :
104
+ self .handle .verbose_log (f'(Retry { retries } /{ retry_amount } ) Processing Source File: { device_type ["src" ]} ' )
105
+
106
+ # Remove file base path
107
+ src_file = device_type ["src" ]
108
+ del device_type ["src" ]
109
+
110
+ # Pre-process front/rear_image flag, remove it if present
111
+ saved_images = {}
112
+ image_base = os .path .dirname (src_file ).replace ("device-types" ,"elevation-images" )
113
+ for i in ["front_image" ,"rear_image" ]:
114
+ if i in device_type :
115
+ if device_type [i ]:
116
+ image_glob = f"{ image_base } /{ device_type ['slug' ]} .{ i .split ('_' )[0 ]} .*"
117
+ images = glob .glob (image_glob , recursive = False )
118
+ if images :
119
+ saved_images [i ] = images [0 ]
120
+ else :
121
+ self .handle .log (f"Error locating image file using '{ image_glob } '" )
122
+ del device_type [i ]
123
+
124
+ try :
125
+ dt = self .device_types .existing_device_types [device_type ["model" ]]
126
+ self .handle .verbose_log (f'Device Type Exists: { dt .manufacturer .name } - { dt .model } - { dt .id } ' )
127
+ except KeyError :
128
+ try :
129
+ dt = self .netbox .dcim .device_types .create (device_type )
130
+ self .counter .update ({'added' : 1 })
131
+ self .handle .verbose_log (f'Device Type Created: { dt .manufacturer .name } - { dt .model } - { dt .id } ' )
132
+ except pynetbox .RequestError as e :
133
+ self .handle .log (f'Error { e .error } creating device type: { device_type ["manufacturer" ]["name" ]} { device_type ["model" ]} ' )
134
+ retries += 1
135
+ continue
136
+
137
+ if "interfaces" in device_type :
138
+ self .device_types .create_interfaces (device_type ["interfaces" ], dt .id )
139
+ if "power-ports" in device_type :
140
+ self .device_types .create_power_ports (device_type ["power-ports" ], dt .id )
141
+ if "power-port" in device_type :
142
+ self .device_types .create_power_ports (device_type ["power-port" ], dt .id )
143
+ if "console-ports" in device_type :
144
+ self .device_types .create_console_ports (device_type ["console-ports" ], dt .id )
145
+ if "power-outlets" in device_type :
146
+ self .device_types .create_power_outlets (device_type ["power-outlets" ], dt .id )
147
+ if "console-server-ports" in device_type :
148
+ self .device_types .create_console_server_ports (device_type ["console-server-ports" ], dt .id )
149
+ if "rear-ports" in device_type :
150
+ self .device_types .create_rear_ports (device_type ["rear-ports" ], dt .id )
151
+ if "front-ports" in device_type :
152
+ self .device_types .create_front_ports (device_type ["front-ports" ], dt .id )
153
+ if "device-bays" in device_type :
154
+ self .device_types .create_device_bays (device_type ["device-bays" ], dt .id )
155
+ if self .modules and 'module-bays' in device_type :
156
+ self .device_types .create_module_bays (device_type ['module-bays' ], dt .id )
157
+
158
+ # Finally, update images if any
159
+ if saved_images :
160
+ self .device_types .upload_images (self .url , self .token , saved_images , dt .id )
161
+
162
+ # We successfully processed the device. Don't retry it.
163
+ retries = retry_amount
164
+ except (http .client .RemoteDisconnected , requests .exceptions .ConnectionError ) as e :
165
+ retries += 1
166
+ self .counter .update ({'connection_errors' : 1 })
167
+ self .handle .log (f'A connection error occurred (Count: { self .counter ["connection_errors" ]} )! Waiting { self .retry_delay } seconds then retrying... Exception: { e } ' )
168
+
169
+ # As a connection error has just occurred, we should give the remote end a moment then reconnect.
170
+ time .sleep (self .retry_delay )
171
+ self .connect_api ()
116
172
continue
117
173
118
- if "interfaces" in device_type :
119
- self .device_types .create_interfaces (device_type ["interfaces" ], dt .id )
120
- if "power-ports" in device_type :
121
- self .device_types .create_power_ports (device_type ["power-ports" ], dt .id )
122
- if "power-port" in device_type :
123
- self .device_types .create_power_ports (device_type ["power-port" ], dt .id )
124
- if "console-ports" in device_type :
125
- self .device_types .create_console_ports (device_type ["console-ports" ], dt .id )
126
- if "power-outlets" in device_type :
127
- self .device_types .create_power_outlets (device_type ["power-outlets" ], dt .id )
128
- if "console-server-ports" in device_type :
129
- self .device_types .create_console_server_ports (device_type ["console-server-ports" ], dt .id )
130
- if "rear-ports" in device_type :
131
- self .device_types .create_rear_ports (device_type ["rear-ports" ], dt .id )
132
- if "front-ports" in device_type :
133
- self .device_types .create_front_ports (device_type ["front-ports" ], dt .id )
134
- if "device-bays" in device_type :
135
- self .device_types .create_device_bays (device_type ["device-bays" ], dt .id )
136
- if self .modules and 'module-bays' in device_type :
137
- self .device_types .create_module_bays (device_type ['module-bays' ], dt .id )
138
-
139
- # Finally, update images if any
140
- if saved_images :
141
- self .device_types .upload_images (self .url , self .token , saved_images , dt .id )
142
174
143
175
def create_module_types (self , module_types ):
144
176
all_module_types = {}
@@ -147,37 +179,63 @@ def create_module_types(self, module_types):
147
179
all_module_types [curr_nb_mt .manufacturer .slug ] = {}
148
180
149
181
all_module_types [curr_nb_mt .manufacturer .slug ][curr_nb_mt .model ] = curr_nb_mt
182
+
183
+ retry_amount = 2
184
+ # Treat the original data as immutable in case we encounter a connection error.
185
+ for curr_mt_immutable in module_types :
186
+ # In the event we hit a ConnectionReset error on this item, we want to retry it.
187
+ # If it fails twice, assume it's an issue with the device_type
188
+ retries = 0
150
189
190
+ while retries < retry_amount :
191
+ curr_mt = copy .deepcopy (curr_mt_immutable ) # Can this be a copy.copy(curr_mt_immutable)?
151
192
152
- for curr_mt in module_types :
153
- try :
154
- module_type_res = all_module_types [curr_mt ['manufacturer' ]['slug' ]][curr_mt ["model" ]]
155
- self .handle .verbose_log (f'Module Type Exists: { module_type_res .manufacturer .name } - '
156
- + f'{ module_type_res .model } - { module_type_res .id } ' )
157
- except KeyError :
158
193
try :
159
- module_type_res = self .netbox .dcim .module_types .create (curr_mt )
160
- self .counter .update ({'module_added' : 1 })
161
- self .handle .verbose_log (f'Module Type Created: { module_type_res .manufacturer .name } - '
162
- + f'{ module_type_res .model } - { module_type_res .id } ' )
163
- except pynetbox .RequestError as exce :
164
- self .handle .log (f"Error '{ exce .error } ' creating module type: " +
165
- f"{ curr_mt } " )
166
-
167
- if "interfaces" in curr_mt :
168
- self .device_types .create_module_interfaces (curr_mt ["interfaces" ], module_type_res .id )
169
- if "power-ports" in curr_mt :
170
- self .device_types .create_module_power_ports (curr_mt ["power-ports" ], module_type_res .id )
171
- if "console-ports" in curr_mt :
172
- self .device_types .create_module_console_ports (curr_mt ["console-ports" ], module_type_res .id )
173
- if "power-outlets" in curr_mt :
174
- self .device_types .create_module_power_outlets (curr_mt ["power-outlets" ], module_type_res .id )
175
- if "console-server-ports" in curr_mt :
176
- self .device_types .create_module_console_server_ports (curr_mt ["console-server-ports" ], module_type_res .id )
177
- if "rear-ports" in curr_mt :
178
- self .device_types .create_module_rear_ports (curr_mt ["rear-ports" ], module_type_res .id )
179
- if "front-ports" in curr_mt :
180
- self .device_types .create_module_front_ports (curr_mt ["front-ports" ], module_type_res .id )
194
+ if retries == 0 :
195
+ self .handle .verbose_log (f'Processing Source File: { curr_mt ["src" ]} ' )
196
+ else :
197
+ self .handle .verbose_log (f'(Retry { retries } /{ retry_amount } ) Processing Source File: { curr_mt ["src" ]} ' )
198
+
199
+ try :
200
+ module_type_res = all_module_types [curr_mt ['manufacturer' ]['slug' ]][curr_mt ["model" ]]
201
+ self .handle .verbose_log (f'Module Type Exists: { module_type_res .manufacturer .name } - { module_type_res .model } - { module_type_res .id } ' )
202
+ except KeyError :
203
+ try :
204
+ module_type_res = self .netbox .dcim .module_types .create (curr_mt )
205
+ self .counter .update ({'module_added' : 1 })
206
+ self .handle .verbose_log (f'Module Type Created: { module_type_res .manufacturer .name } - { module_type_res .model } - { module_type_res .id } ' )
207
+ except pynetbox .RequestError as exce :
208
+ self .handle .log (f"Error '{ exce .error } ' creating module type: { curr_mt ["manufacturer" ]} { curr_mt ["model" ]} { curr_mt ["part_number" ]} " )
209
+ retries += 1
210
+ continue
211
+
212
+ if "interfaces" in curr_mt :
213
+ self .device_types .create_module_interfaces (curr_mt ["interfaces" ], module_type_res .id )
214
+ if "power-ports" in curr_mt :
215
+ self .device_types .create_module_power_ports (curr_mt ["power-ports" ], module_type_res .id )
216
+ if "console-ports" in curr_mt :
217
+ self .device_types .create_module_console_ports (curr_mt ["console-ports" ], module_type_res .id )
218
+ if "power-outlets" in curr_mt :
219
+ self .device_types .create_module_power_outlets (curr_mt ["power-outlets" ], module_type_res .id )
220
+ if "console-server-ports" in curr_mt :
221
+ self .device_types .create_module_console_server_ports (curr_mt ["console-server-ports" ], module_type_res .id )
222
+ if "rear-ports" in curr_mt :
223
+ self .device_types .create_module_rear_ports (curr_mt ["rear-ports" ], module_type_res .id )
224
+ if "front-ports" in curr_mt :
225
+ self .device_types .create_module_front_ports (curr_mt ["front-ports" ], module_type_res .id )
226
+
227
+ # We successfully processed the device. Don't retry it.
228
+ retries = retry_amount
229
+
230
+ except (http .client .RemoteDisconnected , requests .exceptions .ConnectionError ) as e :
231
+ retries += 1
232
+ self .counter .update ({'connection_errors' : 1 })
233
+ self .handle .log (f'A connection error occurred (Count: { self .counter ["connection_errors" ]} )! Waiting { self .retry_delay } seconds then retrying... Exception: { e } ' )
234
+
235
+ # As a connection error has just occurred, we should give the remote end a moment then reconnect.
236
+ time .sleep (self .retry_delay )
237
+ self .connect_api ()
238
+ continue
181
239
182
240
class DeviceTypes :
183
241
def __new__ (cls , * args , ** kwargs ):
@@ -480,6 +538,10 @@ def upload_images(self,baseurl,token,images,device_type):
480
538
481
539
files = { i : (os .path .basename (f ), open (f ,"rb" ) ) for i ,f in images .items () }
482
540
response = requests .patch (url , headers = headers , files = files , verify = (not self .ignore_ssl ))
483
-
484
- self .handle .log ( f'Images { images } updated at { url } : { response } ' )
541
+
542
+ if response .status_code == 500 :
543
+ raise Exception (f"Remote server failed to write images. Ensure your media directory exists and is writable! - { response } " )
544
+ else :
545
+ self .handle .log ( f'Images { images } updated at { url } : { response } (Code { response .status_code } )' )
546
+
485
547
self .counter ["images" ] += len (images )
0 commit comments