ipfabric_netbox 4.2.1__py3-none-any.whl → 4.2.1b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ipfabric_netbox/__init__.py +1 -1
- ipfabric_netbox/api/__init__.py +0 -1
- ipfabric_netbox/api/serializers.py +147 -90
- ipfabric_netbox/api/urls.py +4 -4
- ipfabric_netbox/api/views.py +18 -19
- ipfabric_netbox/choices.py +0 -12
- ipfabric_netbox/filtersets.py +67 -4
- ipfabric_netbox/forms.py +92 -140
- ipfabric_netbox/graphql/__init__.py +23 -0
- ipfabric_netbox/graphql/enums.py +35 -0
- ipfabric_netbox/graphql/filters.py +317 -0
- ipfabric_netbox/graphql/schema.py +101 -0
- ipfabric_netbox/graphql/types.py +216 -0
- ipfabric_netbox/migrations/0016_tags_and_changelog_for_snapshots.py +31 -0
- ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py +17 -0
- ipfabric_netbox/migrations/0018_remove_type_field.py +17 -0
- ipfabric_netbox/models.py +10 -10
- ipfabric_netbox/tables.py +30 -9
- ipfabric_netbox/tests/api/__init__.py +0 -0
- ipfabric_netbox/tests/api/test_api.py +879 -0
- ipfabric_netbox/tests/test_forms.py +1440 -0
- ipfabric_netbox/tests/test_models.py +47 -11
- ipfabric_netbox/utilities/ipfutils.py +43 -23
- ipfabric_netbox/views.py +6 -8
- {ipfabric_netbox-4.2.1.dist-info → ipfabric_netbox-4.2.1b2.dist-info}/METADATA +6 -6
- {ipfabric_netbox-4.2.1.dist-info → ipfabric_netbox-4.2.1b2.dist-info}/RECORD +27 -18
- ipfabric_netbox/api/nested_serializers.py +0 -78
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync_list.html +0 -71
- {ipfabric_netbox-4.2.1.dist-info → ipfabric_netbox-4.2.1b2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
from django.utils import timezone
|
|
3
|
+
from rest_framework import status
|
|
4
|
+
from utilities.testing import APIViewTestCases
|
|
5
|
+
|
|
6
|
+
from ipfabric_netbox.models import IPFabricData
|
|
7
|
+
from ipfabric_netbox.models import IPFabricIngestion
|
|
8
|
+
from ipfabric_netbox.models import IPFabricIngestionIssue
|
|
9
|
+
from ipfabric_netbox.models import IPFabricRelationshipField
|
|
10
|
+
from ipfabric_netbox.models import IPFabricSnapshot
|
|
11
|
+
from ipfabric_netbox.models import IPFabricSource
|
|
12
|
+
from ipfabric_netbox.models import IPFabricSync
|
|
13
|
+
from ipfabric_netbox.models import IPFabricTransformField
|
|
14
|
+
from ipfabric_netbox.models import IPFabricTransformMap
|
|
15
|
+
from ipfabric_netbox.models import IPFabricTransformMapGroup
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
BASE = "/api/plugins/ipfabric/"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class IPFabricTransformMapGroupTest(APIViewTestCases.APIViewTestCase):
|
|
22
|
+
model = IPFabricTransformMapGroup
|
|
23
|
+
graphql_base_name = "ipfabric_transform_map_group"
|
|
24
|
+
brief_fields = [
|
|
25
|
+
"description",
|
|
26
|
+
"id",
|
|
27
|
+
"name",
|
|
28
|
+
]
|
|
29
|
+
create_data = [
|
|
30
|
+
{"name": "Group A"},
|
|
31
|
+
{"name": "Group B", "description": "Description of group B"},
|
|
32
|
+
{"name": "Group C"},
|
|
33
|
+
]
|
|
34
|
+
bulk_update_data = {
|
|
35
|
+
"description": "Updated Group Description",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def _get_list_url(self):
|
|
39
|
+
return f"{BASE}transform-map-group/"
|
|
40
|
+
|
|
41
|
+
def _get_detail_url(self, instance):
|
|
42
|
+
return f"{BASE}transform-map-group/{instance.pk}/"
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def setUpTestData(cls):
|
|
46
|
+
IPFabricTransformMapGroup.objects.create(name="Group D")
|
|
47
|
+
IPFabricTransformMapGroup.objects.create(
|
|
48
|
+
name="Group E", description="Description of group E"
|
|
49
|
+
)
|
|
50
|
+
IPFabricTransformMapGroup.objects.create(name="Group F")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class IPFabricTransformMapTest(APIViewTestCases.APIViewTestCase):
|
|
54
|
+
model = IPFabricTransformMap
|
|
55
|
+
graphql_base_name = "ipfabric_transform_map"
|
|
56
|
+
brief_fields = [
|
|
57
|
+
"group",
|
|
58
|
+
"id",
|
|
59
|
+
"name",
|
|
60
|
+
"source_model",
|
|
61
|
+
"target_model",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
def _get_list_url(self):
|
|
65
|
+
return f"{BASE}transform-map/"
|
|
66
|
+
|
|
67
|
+
def _get_detail_url(self, instance):
|
|
68
|
+
return f"{BASE}transform-map/{instance.pk}/"
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def setUpTestData(cls):
|
|
72
|
+
groups = (
|
|
73
|
+
IPFabricTransformMapGroup.objects.create(name="Group A"),
|
|
74
|
+
IPFabricTransformMapGroup.objects.create(name="Group B"),
|
|
75
|
+
IPFabricTransformMapGroup.objects.create(name="Group C"),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
IPFabricTransformMap.objects.create(
|
|
79
|
+
name="TransformMap D",
|
|
80
|
+
source_model="site",
|
|
81
|
+
target_model=ContentType.objects.get(app_label="dcim", model="site"),
|
|
82
|
+
)
|
|
83
|
+
IPFabricTransformMap.objects.create(
|
|
84
|
+
name="TransformMap E",
|
|
85
|
+
source_model="site",
|
|
86
|
+
target_model=ContentType.objects.get(app_label="dcim", model="site"),
|
|
87
|
+
group=groups[0],
|
|
88
|
+
)
|
|
89
|
+
IPFabricTransformMap.objects.create(
|
|
90
|
+
name="TransformMap F",
|
|
91
|
+
source_model="ipaddress",
|
|
92
|
+
target_model=ContentType.objects.get(app_label="ipam", model="ipaddress"),
|
|
93
|
+
group=groups[0],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
cls.create_data = [
|
|
97
|
+
{
|
|
98
|
+
"name": "Transform Map A",
|
|
99
|
+
"source_model": "site",
|
|
100
|
+
"target_model": "dcim.site",
|
|
101
|
+
"group": groups[1].pk,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "Transform Map B",
|
|
105
|
+
"source_model": "device",
|
|
106
|
+
"target_model": "dcim.device",
|
|
107
|
+
"group": groups[1].pk,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "Transform Map C",
|
|
111
|
+
"source_model": "vrf",
|
|
112
|
+
"target_model": "ipam.vrf",
|
|
113
|
+
"group": groups[1].pk,
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
cls.bulk_update_data = {
|
|
117
|
+
"group": groups[2].pk,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class IPFabricTransformFieldTest(APIViewTestCases.APIViewTestCase):
|
|
122
|
+
model = IPFabricTransformField
|
|
123
|
+
graphql_base_name = "ipfabric_transform_field"
|
|
124
|
+
# in this case brief fields are the same, but they are needed fot the test
|
|
125
|
+
brief_fields = [
|
|
126
|
+
"coalesce",
|
|
127
|
+
"id",
|
|
128
|
+
"source_field",
|
|
129
|
+
"target_field",
|
|
130
|
+
"template",
|
|
131
|
+
"transform_map",
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
def _get_list_url(self):
|
|
135
|
+
return f"{BASE}transform-field/"
|
|
136
|
+
|
|
137
|
+
def _get_detail_url(self, instance):
|
|
138
|
+
return f"{BASE}transform-field/{instance.pk}/"
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def setUpTestData(cls):
|
|
142
|
+
# Create groups for transform maps
|
|
143
|
+
groups = (
|
|
144
|
+
IPFabricTransformMapGroup.objects.create(name="Field Test Group A"),
|
|
145
|
+
IPFabricTransformMapGroup.objects.create(name="Field Test Group B"),
|
|
146
|
+
IPFabricTransformMapGroup.objects.create(name="Field Test Group C"),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Create transform maps for the fields to reference
|
|
150
|
+
transform_maps = [
|
|
151
|
+
IPFabricTransformMap.objects.create(
|
|
152
|
+
name="Field Map A",
|
|
153
|
+
source_model="site",
|
|
154
|
+
target_model=ContentType.objects.get(app_label="dcim", model="site"),
|
|
155
|
+
group=groups[0],
|
|
156
|
+
),
|
|
157
|
+
IPFabricTransformMap.objects.create(
|
|
158
|
+
name="Field Map B",
|
|
159
|
+
source_model="device",
|
|
160
|
+
target_model=ContentType.objects.get(app_label="dcim", model="device"),
|
|
161
|
+
group=groups[0],
|
|
162
|
+
),
|
|
163
|
+
IPFabricTransformMap.objects.create(
|
|
164
|
+
name="Field Map C",
|
|
165
|
+
source_model="ipaddress",
|
|
166
|
+
target_model=ContentType.objects.get(
|
|
167
|
+
app_label="ipam", model="ipaddress"
|
|
168
|
+
),
|
|
169
|
+
group=groups[1],
|
|
170
|
+
),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
# Create existing transform fields for testing
|
|
174
|
+
IPFabricTransformField.objects.create(
|
|
175
|
+
transform_map=transform_maps[0],
|
|
176
|
+
source_field="hostname",
|
|
177
|
+
target_field="name",
|
|
178
|
+
coalesce=False,
|
|
179
|
+
template="{{ hostname }}",
|
|
180
|
+
)
|
|
181
|
+
IPFabricTransformField.objects.create(
|
|
182
|
+
transform_map=transform_maps[0],
|
|
183
|
+
source_field="site_name",
|
|
184
|
+
target_field="description",
|
|
185
|
+
coalesce=True,
|
|
186
|
+
template="Site: {{ site_name }}",
|
|
187
|
+
)
|
|
188
|
+
IPFabricTransformField.objects.create(
|
|
189
|
+
transform_map=transform_maps[1],
|
|
190
|
+
source_field="device_type",
|
|
191
|
+
target_field="platform",
|
|
192
|
+
coalesce=False,
|
|
193
|
+
template="",
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
cls.create_data = [
|
|
197
|
+
{
|
|
198
|
+
"transform_map": transform_maps[1].pk,
|
|
199
|
+
"source_field": "ip_address",
|
|
200
|
+
"target_field": "primary_ip4",
|
|
201
|
+
"coalesce": False,
|
|
202
|
+
"template": "{{ ip_address }}",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"transform_map": transform_maps[1].pk,
|
|
206
|
+
"source_field": "location",
|
|
207
|
+
"target_field": "site",
|
|
208
|
+
"coalesce": True,
|
|
209
|
+
"template": "{{ location | default('Unknown') }}",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"transform_map": transform_maps[2].pk,
|
|
213
|
+
"source_field": "subnet",
|
|
214
|
+
"target_field": "address",
|
|
215
|
+
"coalesce": False,
|
|
216
|
+
"template": "{{ subnet }}",
|
|
217
|
+
},
|
|
218
|
+
]
|
|
219
|
+
cls.bulk_update_data = {
|
|
220
|
+
"coalesce": True,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class IPFabricRelationshipFieldTest(APIViewTestCases.APIViewTestCase):
|
|
225
|
+
model = IPFabricRelationshipField
|
|
226
|
+
graphql_base_name = "ipfabric_relationship_field"
|
|
227
|
+
# in this case brief fields are the same, but they are needed fot the test
|
|
228
|
+
brief_fields = [
|
|
229
|
+
"coalesce",
|
|
230
|
+
"id",
|
|
231
|
+
"source_model",
|
|
232
|
+
"target_field",
|
|
233
|
+
"template",
|
|
234
|
+
"transform_map",
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
def _get_list_url(self):
|
|
238
|
+
return f"{BASE}relationship-field/"
|
|
239
|
+
|
|
240
|
+
def _get_detail_url(self, instance):
|
|
241
|
+
return f"{BASE}relationship-field/{instance.pk}/"
|
|
242
|
+
|
|
243
|
+
@classmethod
|
|
244
|
+
def setUpTestData(cls):
|
|
245
|
+
# Create groups for transform maps
|
|
246
|
+
groups = (
|
|
247
|
+
IPFabricTransformMapGroup.objects.create(name="Relationship Test Group A"),
|
|
248
|
+
IPFabricTransformMapGroup.objects.create(name="Relationship Test Group B"),
|
|
249
|
+
IPFabricTransformMapGroup.objects.create(name="Relationship Test Group C"),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Create transform maps for the relationship fields to reference
|
|
253
|
+
transform_maps = [
|
|
254
|
+
IPFabricTransformMap.objects.create(
|
|
255
|
+
name="Relationship Map A",
|
|
256
|
+
source_model="site",
|
|
257
|
+
target_model=ContentType.objects.get(app_label="dcim", model="site"),
|
|
258
|
+
group=groups[0],
|
|
259
|
+
),
|
|
260
|
+
IPFabricTransformMap.objects.create(
|
|
261
|
+
name="Relationship Map B",
|
|
262
|
+
source_model="device",
|
|
263
|
+
target_model=ContentType.objects.get(app_label="dcim", model="device"),
|
|
264
|
+
group=groups[0],
|
|
265
|
+
),
|
|
266
|
+
IPFabricTransformMap.objects.create(
|
|
267
|
+
name="Relationship Map C",
|
|
268
|
+
source_model="ipaddress",
|
|
269
|
+
target_model=ContentType.objects.get(
|
|
270
|
+
app_label="ipam", model="ipaddress"
|
|
271
|
+
),
|
|
272
|
+
group=groups[1],
|
|
273
|
+
),
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
# Create existing relationship fields for testing
|
|
277
|
+
IPFabricRelationshipField.objects.create(
|
|
278
|
+
transform_map=transform_maps[0],
|
|
279
|
+
source_model=ContentType.objects.get(app_label="dcim", model="site"),
|
|
280
|
+
target_field="location",
|
|
281
|
+
coalesce=False,
|
|
282
|
+
template="{{ site.location_id }}",
|
|
283
|
+
)
|
|
284
|
+
IPFabricRelationshipField.objects.create(
|
|
285
|
+
transform_map=transform_maps[0],
|
|
286
|
+
source_model=ContentType.objects.get(app_label="dcim", model="device"),
|
|
287
|
+
target_field="site",
|
|
288
|
+
coalesce=True,
|
|
289
|
+
template="{{ device.site_id }}",
|
|
290
|
+
)
|
|
291
|
+
IPFabricRelationshipField.objects.create(
|
|
292
|
+
transform_map=transform_maps[1],
|
|
293
|
+
source_model=ContentType.objects.get(app_label="ipam", model="ipaddress"),
|
|
294
|
+
target_field="interface",
|
|
295
|
+
coalesce=False,
|
|
296
|
+
template="{{ ipaddress.interface_id }}",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
cls.create_data = [
|
|
300
|
+
{
|
|
301
|
+
"transform_map": transform_maps[1].pk,
|
|
302
|
+
"source_model": "dcim.device",
|
|
303
|
+
"target_field": "primary_ip4",
|
|
304
|
+
"coalesce": False,
|
|
305
|
+
"template": "{{ device.primary_ip4_id }}",
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
"transform_map": transform_maps[1].pk,
|
|
309
|
+
"source_model": "dcim.interface",
|
|
310
|
+
"target_field": "device",
|
|
311
|
+
"coalesce": True,
|
|
312
|
+
"template": "{{ interface.device_id }}",
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"transform_map": transform_maps[2].pk,
|
|
316
|
+
"source_model": "ipam.prefix",
|
|
317
|
+
"target_field": "vrf",
|
|
318
|
+
"coalesce": False,
|
|
319
|
+
"template": "{{ prefix.vrf_id }}",
|
|
320
|
+
},
|
|
321
|
+
]
|
|
322
|
+
cls.bulk_update_data = {
|
|
323
|
+
"coalesce": True,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class IPFabricSourceTest(APIViewTestCases.APIViewTestCase):
|
|
328
|
+
model = IPFabricSource
|
|
329
|
+
brief_fields = [
|
|
330
|
+
"display",
|
|
331
|
+
"id",
|
|
332
|
+
"name",
|
|
333
|
+
"status",
|
|
334
|
+
"type",
|
|
335
|
+
"url",
|
|
336
|
+
]
|
|
337
|
+
bulk_update_data = {
|
|
338
|
+
"url": "https://updated.local",
|
|
339
|
+
}
|
|
340
|
+
graphql_base_name = "ipfabric_source"
|
|
341
|
+
|
|
342
|
+
def _get_list_url(self):
|
|
343
|
+
return f"{BASE}source/"
|
|
344
|
+
|
|
345
|
+
def _get_detail_url(self, instance):
|
|
346
|
+
return f"{BASE}source/{instance.pk}/"
|
|
347
|
+
|
|
348
|
+
@classmethod
|
|
349
|
+
def setUpTestData(cls):
|
|
350
|
+
IPFabricSource.objects.create(
|
|
351
|
+
name="Source A",
|
|
352
|
+
url="https://a.local",
|
|
353
|
+
parameters={"auth": "t", "verify": True},
|
|
354
|
+
last_synced=timezone.now(),
|
|
355
|
+
)
|
|
356
|
+
IPFabricSource.objects.create(
|
|
357
|
+
name="Source B",
|
|
358
|
+
url="https://b.local",
|
|
359
|
+
parameters={"auth": "t", "verify": False},
|
|
360
|
+
last_synced=timezone.now(),
|
|
361
|
+
)
|
|
362
|
+
IPFabricSource.objects.create(
|
|
363
|
+
name="Source C",
|
|
364
|
+
url="https://c.local",
|
|
365
|
+
parameters={"auth": "t", "verify": False},
|
|
366
|
+
last_synced=timezone.now(),
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
cls.create_data = [
|
|
370
|
+
{
|
|
371
|
+
"name": "NewSrc 1",
|
|
372
|
+
"url": "https://nb1.example",
|
|
373
|
+
"parameters": {"auth": "t", "verify": False},
|
|
374
|
+
"type": "local",
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"name": "NewSrc 2",
|
|
378
|
+
"url": "https://nb2.example",
|
|
379
|
+
"parameters": {"auth": "t", "verify": True},
|
|
380
|
+
"type": "local",
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"name": "NewSrc 3",
|
|
384
|
+
"url": "https://nb3.example",
|
|
385
|
+
"parameters": {"auth": "t", "verify": True},
|
|
386
|
+
"type": "local",
|
|
387
|
+
},
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class IPFabricSnapshotTest(
|
|
392
|
+
APIViewTestCases.GetObjectViewTestCase,
|
|
393
|
+
APIViewTestCases.ListObjectsViewTestCase,
|
|
394
|
+
APIViewTestCases.GraphQLTestCase,
|
|
395
|
+
):
|
|
396
|
+
model = IPFabricSnapshot
|
|
397
|
+
graphql_base_name = "ipfabric_snapshot"
|
|
398
|
+
brief_fields = [
|
|
399
|
+
"data",
|
|
400
|
+
"date",
|
|
401
|
+
"display",
|
|
402
|
+
"id",
|
|
403
|
+
"name",
|
|
404
|
+
"snapshot_id",
|
|
405
|
+
"source",
|
|
406
|
+
"status",
|
|
407
|
+
]
|
|
408
|
+
|
|
409
|
+
def _get_list_url(self):
|
|
410
|
+
return f"{BASE}snapshot/"
|
|
411
|
+
|
|
412
|
+
def _get_detail_url(self, instance):
|
|
413
|
+
return f"{BASE}snapshot/{instance.pk}/"
|
|
414
|
+
|
|
415
|
+
@classmethod
|
|
416
|
+
def setUpTestData(cls):
|
|
417
|
+
sources = (
|
|
418
|
+
IPFabricSource.objects.create(
|
|
419
|
+
name="Source A",
|
|
420
|
+
url="https://src.local",
|
|
421
|
+
parameters={"auth": "t", "verify": True},
|
|
422
|
+
last_synced=timezone.now(),
|
|
423
|
+
),
|
|
424
|
+
IPFabricSource.objects.create(
|
|
425
|
+
name="Source B",
|
|
426
|
+
url="https://srcb.local",
|
|
427
|
+
parameters={"auth": "t", "verify": True},
|
|
428
|
+
last_synced=timezone.now(),
|
|
429
|
+
),
|
|
430
|
+
IPFabricSource.objects.create(
|
|
431
|
+
name="Source C",
|
|
432
|
+
url="https://srcc.local",
|
|
433
|
+
parameters={"auth": "t", "verify": True},
|
|
434
|
+
last_synced=timezone.now(),
|
|
435
|
+
),
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
cls.snapshots = (
|
|
439
|
+
IPFabricSnapshot.objects.create(
|
|
440
|
+
name="Snapshot One",
|
|
441
|
+
source=sources[0],
|
|
442
|
+
snapshot_id="snap-1",
|
|
443
|
+
status="loaded",
|
|
444
|
+
data={"sites": ["SiteA", "SiteB", "RemoteC"]},
|
|
445
|
+
date=timezone.now(),
|
|
446
|
+
last_updated=timezone.now(),
|
|
447
|
+
),
|
|
448
|
+
IPFabricSnapshot.objects.create(
|
|
449
|
+
name="Another Name",
|
|
450
|
+
source=sources[0],
|
|
451
|
+
snapshot_id="snap-2",
|
|
452
|
+
status="loaded",
|
|
453
|
+
data={"sites": []},
|
|
454
|
+
date=timezone.now(),
|
|
455
|
+
last_updated=timezone.now(),
|
|
456
|
+
),
|
|
457
|
+
IPFabricSnapshot.objects.create(
|
|
458
|
+
name="Third Snapshot",
|
|
459
|
+
source=sources[0],
|
|
460
|
+
snapshot_id="snap-3",
|
|
461
|
+
status="unloaded",
|
|
462
|
+
data={"sites": ["SiteD"]},
|
|
463
|
+
date=timezone.now(),
|
|
464
|
+
last_updated=timezone.now(),
|
|
465
|
+
),
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
def test_sites_action_lists_all_and_filters(self):
|
|
469
|
+
self.add_permissions("ipfabric_netbox.view_ipfabricsnapshot")
|
|
470
|
+
# list all
|
|
471
|
+
url = f"{BASE}snapshot/{self.snapshots[0].pk}/sites/"
|
|
472
|
+
resp = self.client.get(url, **self.header)
|
|
473
|
+
self.assertHttpStatus(resp, status.HTTP_200_OK)
|
|
474
|
+
body = resp.json()
|
|
475
|
+
self.assertIn(body.__class__, (list, dict))
|
|
476
|
+
if isinstance(body, dict):
|
|
477
|
+
labels = [i["name"] for i in body["results"]]
|
|
478
|
+
self.assertEqual(labels, self.snapshots[0].data["sites"])
|
|
479
|
+
# filter
|
|
480
|
+
url = f"{BASE}snapshot/{self.snapshots[0].pk}/sites/?q=site"
|
|
481
|
+
resp = self.client.get(url, **self.header)
|
|
482
|
+
self.assertHttpStatus(resp, status.HTTP_200_OK)
|
|
483
|
+
body = resp.json()
|
|
484
|
+
if isinstance(body, dict) and body.get("results"):
|
|
485
|
+
labels = [i["name"].lower() for i in body["results"]]
|
|
486
|
+
self.assertTrue(all("site" in name for name in labels))
|
|
487
|
+
|
|
488
|
+
def test_raw_patch_and_delete(self):
|
|
489
|
+
self.add_permissions(
|
|
490
|
+
"ipfabric_netbox.view_ipfabricsnapshot",
|
|
491
|
+
"ipfabric_netbox.change_ipfabricsnapshot",
|
|
492
|
+
"ipfabric_netbox.delete_ipfabricsnapshot",
|
|
493
|
+
)
|
|
494
|
+
# initial count
|
|
495
|
+
self.assertEqual(
|
|
496
|
+
IPFabricData.objects.filter(snapshot_data=self.snapshots[0]).count(), 0
|
|
497
|
+
)
|
|
498
|
+
# PATCH raw
|
|
499
|
+
url = f"{BASE}snapshot/{self.snapshots[0].pk}/raw/"
|
|
500
|
+
payload = {
|
|
501
|
+
"data": [
|
|
502
|
+
{"data": {"example": 1}, "type": "device"},
|
|
503
|
+
{"data": {"foo": "bar"}, "type": "interface"},
|
|
504
|
+
]
|
|
505
|
+
}
|
|
506
|
+
resp = self.client.patch(url, data=payload, format="json", **self.header)
|
|
507
|
+
self.assertHttpStatus(resp, status.HTTP_200_OK)
|
|
508
|
+
self.assertEqual(resp.data, {"status": "success"})
|
|
509
|
+
self.assertEqual(
|
|
510
|
+
IPFabricData.objects.filter(snapshot_data=self.snapshots[0]).count(), 2
|
|
511
|
+
)
|
|
512
|
+
# DELETE raw
|
|
513
|
+
resp = self.client.delete(url, **self.header)
|
|
514
|
+
self.assertHttpStatus(resp, status.HTTP_200_OK)
|
|
515
|
+
self.assertEqual(resp.data, {"status": "success"})
|
|
516
|
+
self.assertEqual(
|
|
517
|
+
IPFabricData.objects.filter(snapshot_data=self.snapshots[0]).count(), 0
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
class IPFabricSyncTest(APIViewTestCases.APIViewTestCase):
|
|
522
|
+
model = IPFabricSync
|
|
523
|
+
graphql_base_name = "ipfabric_sync"
|
|
524
|
+
brief_fields = [
|
|
525
|
+
"auto_merge",
|
|
526
|
+
"id",
|
|
527
|
+
"last_synced",
|
|
528
|
+
"name",
|
|
529
|
+
"parameters",
|
|
530
|
+
"status",
|
|
531
|
+
]
|
|
532
|
+
create_data = [
|
|
533
|
+
{
|
|
534
|
+
"name": "Test Sync A",
|
|
535
|
+
"parameters": {"site": True, "device": False},
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
"name": "Test Sync B",
|
|
539
|
+
"parameters": {"ipaddress": True, "prefix": True},
|
|
540
|
+
"auto_merge": True,
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
"name": "Test Sync C",
|
|
544
|
+
"parameters": {"device": True, "interface": True},
|
|
545
|
+
"interval": 60,
|
|
546
|
+
},
|
|
547
|
+
]
|
|
548
|
+
bulk_update_data = {
|
|
549
|
+
"auto_merge": True,
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
def _get_list_url(self):
|
|
553
|
+
return f"{BASE}sync/"
|
|
554
|
+
|
|
555
|
+
def _get_detail_url(self, instance):
|
|
556
|
+
return f"{BASE}sync/{instance.pk}/"
|
|
557
|
+
|
|
558
|
+
@classmethod
|
|
559
|
+
def setUpTestData(cls):
|
|
560
|
+
# Create sources for the snapshots
|
|
561
|
+
sources = (
|
|
562
|
+
IPFabricSource.objects.create(
|
|
563
|
+
name="Sync Test Source A",
|
|
564
|
+
url="https://sync-a.local",
|
|
565
|
+
parameters={"auth": "token", "verify": True},
|
|
566
|
+
last_synced=timezone.now(),
|
|
567
|
+
),
|
|
568
|
+
IPFabricSource.objects.create(
|
|
569
|
+
name="Sync Test Source B",
|
|
570
|
+
url="https://sync-b.local",
|
|
571
|
+
parameters={"auth": "token", "verify": False},
|
|
572
|
+
last_synced=timezone.now(),
|
|
573
|
+
),
|
|
574
|
+
IPFabricSource.objects.create(
|
|
575
|
+
name="Sync Test Source C",
|
|
576
|
+
url="https://sync-c.local",
|
|
577
|
+
parameters={"auth": "token", "verify": True},
|
|
578
|
+
last_synced=timezone.now(),
|
|
579
|
+
),
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Create snapshots for the syncs
|
|
583
|
+
snapshots = (
|
|
584
|
+
IPFabricSnapshot.objects.create(
|
|
585
|
+
name="Sync Test Snapshot A",
|
|
586
|
+
source=sources[0],
|
|
587
|
+
snapshot_id="sync-snap-a",
|
|
588
|
+
status="loaded",
|
|
589
|
+
data={"sites": ["SyncSiteA", "SyncSiteB"]},
|
|
590
|
+
date=timezone.now(),
|
|
591
|
+
last_updated=timezone.now(),
|
|
592
|
+
),
|
|
593
|
+
IPFabricSnapshot.objects.create(
|
|
594
|
+
name="Sync Test Snapshot B",
|
|
595
|
+
source=sources[1],
|
|
596
|
+
snapshot_id="sync-snap-b",
|
|
597
|
+
status="loaded",
|
|
598
|
+
data={"devices": ["SyncDevice1", "SyncDevice2"]},
|
|
599
|
+
date=timezone.now(),
|
|
600
|
+
last_updated=timezone.now(),
|
|
601
|
+
),
|
|
602
|
+
IPFabricSnapshot.objects.create(
|
|
603
|
+
name="Sync Test Snapshot C",
|
|
604
|
+
source=sources[2],
|
|
605
|
+
snapshot_id="sync-snap-c",
|
|
606
|
+
status="unloaded",
|
|
607
|
+
data={"interfaces": ["SyncInterface1"]},
|
|
608
|
+
date=timezone.now(),
|
|
609
|
+
last_updated=timezone.now(),
|
|
610
|
+
),
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
# Create syncs for testing
|
|
614
|
+
IPFabricSync.objects.create(
|
|
615
|
+
name="Sync Test D",
|
|
616
|
+
snapshot_data=snapshots[0],
|
|
617
|
+
parameters={"site": True, "device": False},
|
|
618
|
+
)
|
|
619
|
+
IPFabricSync.objects.create(
|
|
620
|
+
name="Sync Test E",
|
|
621
|
+
snapshot_data=snapshots[1],
|
|
622
|
+
parameters={"device": True, "interface": True},
|
|
623
|
+
auto_merge=True,
|
|
624
|
+
)
|
|
625
|
+
IPFabricSync.objects.create(
|
|
626
|
+
name="Sync Test F",
|
|
627
|
+
snapshot_data=snapshots[2],
|
|
628
|
+
parameters={"ipaddress": True, "prefix": False},
|
|
629
|
+
interval=30,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
# Update create_data to reference the snapshots
|
|
633
|
+
cls.create_data[0]["snapshot_data"] = snapshots[0].pk
|
|
634
|
+
cls.create_data[1]["snapshot_data"] = snapshots[1].pk
|
|
635
|
+
cls.create_data[2]["snapshot_data"] = snapshots[2].pk
|
|
636
|
+
cls.create_data[0]["parameters"] = {"site": True, "device": False}
|
|
637
|
+
cls.create_data[1]["parameters"] = {"ipaddress": True, "prefix": True}
|
|
638
|
+
cls.create_data[2]["parameters"] = {"device": True, "interface": True}
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
class IPFabricIngestionTest(
|
|
642
|
+
APIViewTestCases.GetObjectViewTestCase,
|
|
643
|
+
APIViewTestCases.ListObjectsViewTestCase,
|
|
644
|
+
APIViewTestCases.GraphQLTestCase,
|
|
645
|
+
):
|
|
646
|
+
model = IPFabricIngestion
|
|
647
|
+
graphql_base_name = "ipfabric_ingestion"
|
|
648
|
+
brief_fields = [
|
|
649
|
+
"branch",
|
|
650
|
+
"id",
|
|
651
|
+
"name",
|
|
652
|
+
"sync",
|
|
653
|
+
]
|
|
654
|
+
|
|
655
|
+
def _get_list_url(self):
|
|
656
|
+
return f"{BASE}ingestion/"
|
|
657
|
+
|
|
658
|
+
def _get_detail_url(self, instance):
|
|
659
|
+
return f"{BASE}ingestion/{instance.pk}/"
|
|
660
|
+
|
|
661
|
+
@classmethod
|
|
662
|
+
def setUpTestData(cls):
|
|
663
|
+
# Create sources for the snapshots
|
|
664
|
+
sources = (
|
|
665
|
+
IPFabricSource.objects.create(
|
|
666
|
+
name="Ingestion Test Source A",
|
|
667
|
+
url="https://ingestion-a.local",
|
|
668
|
+
parameters={"auth": "token", "verify": True},
|
|
669
|
+
last_synced=timezone.now(),
|
|
670
|
+
),
|
|
671
|
+
IPFabricSource.objects.create(
|
|
672
|
+
name="Ingestion Test Source B",
|
|
673
|
+
url="https://ingestion-b.local",
|
|
674
|
+
parameters={"auth": "token", "verify": False},
|
|
675
|
+
last_synced=timezone.now(),
|
|
676
|
+
),
|
|
677
|
+
IPFabricSource.objects.create(
|
|
678
|
+
name="Ingestion Test Source C",
|
|
679
|
+
url="https://ingestion-c.local",
|
|
680
|
+
parameters={"auth": "token", "verify": True},
|
|
681
|
+
last_synced=timezone.now(),
|
|
682
|
+
),
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
# Create snapshots for the syncs
|
|
686
|
+
snapshots = (
|
|
687
|
+
IPFabricSnapshot.objects.create(
|
|
688
|
+
name="Ingestion Test Snapshot A",
|
|
689
|
+
source=sources[0],
|
|
690
|
+
snapshot_id="ing-snap-a",
|
|
691
|
+
status="loaded",
|
|
692
|
+
data={"sites": ["SiteA", "SiteB"]},
|
|
693
|
+
date=timezone.now(),
|
|
694
|
+
last_updated=timezone.now(),
|
|
695
|
+
),
|
|
696
|
+
IPFabricSnapshot.objects.create(
|
|
697
|
+
name="Ingestion Test Snapshot B",
|
|
698
|
+
source=sources[1],
|
|
699
|
+
snapshot_id="ing-snap-b",
|
|
700
|
+
status="loaded",
|
|
701
|
+
data={"devices": ["Device1", "Device2"]},
|
|
702
|
+
date=timezone.now(),
|
|
703
|
+
last_updated=timezone.now(),
|
|
704
|
+
),
|
|
705
|
+
IPFabricSnapshot.objects.create(
|
|
706
|
+
name="Ingestion Test Snapshot C",
|
|
707
|
+
source=sources[2],
|
|
708
|
+
snapshot_id="ing-snap-c",
|
|
709
|
+
status="unloaded",
|
|
710
|
+
data={"interfaces": ["Interface1"]},
|
|
711
|
+
date=timezone.now(),
|
|
712
|
+
last_updated=timezone.now(),
|
|
713
|
+
),
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
# Create syncs for the ingestions
|
|
717
|
+
syncs = (
|
|
718
|
+
IPFabricSync.objects.create(
|
|
719
|
+
name="Ingestion Test Sync A",
|
|
720
|
+
snapshot_data=snapshots[0],
|
|
721
|
+
parameters={"site": True, "device": False},
|
|
722
|
+
),
|
|
723
|
+
IPFabricSync.objects.create(
|
|
724
|
+
name="Ingestion Test Sync B",
|
|
725
|
+
snapshot_data=snapshots[1],
|
|
726
|
+
parameters={"device": True, "interface": True},
|
|
727
|
+
),
|
|
728
|
+
IPFabricSync.objects.create(
|
|
729
|
+
name="Ingestion Test Sync C",
|
|
730
|
+
snapshot_data=snapshots[2],
|
|
731
|
+
parameters={"ipaddress": True, "prefix": False},
|
|
732
|
+
),
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
# Create ingestions for testing
|
|
736
|
+
IPFabricIngestion.objects.create(sync=syncs[0])
|
|
737
|
+
IPFabricIngestion.objects.create(sync=syncs[1])
|
|
738
|
+
IPFabricIngestion.objects.create(sync=syncs[2])
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
class IPFabricIngestionIssueTest(
|
|
742
|
+
APIViewTestCases.GetObjectViewTestCase,
|
|
743
|
+
APIViewTestCases.ListObjectsViewTestCase,
|
|
744
|
+
APIViewTestCases.GraphQLTestCase,
|
|
745
|
+
):
|
|
746
|
+
model = IPFabricIngestionIssue
|
|
747
|
+
graphql_base_name = "ipfabric_ingestion_issue"
|
|
748
|
+
brief_fields = [
|
|
749
|
+
"exception",
|
|
750
|
+
"id",
|
|
751
|
+
"ingestion",
|
|
752
|
+
"message",
|
|
753
|
+
"model",
|
|
754
|
+
]
|
|
755
|
+
|
|
756
|
+
def _get_list_url(self):
|
|
757
|
+
return f"{BASE}ingestion-issues/"
|
|
758
|
+
|
|
759
|
+
def _get_detail_url(self, instance):
|
|
760
|
+
return f"{BASE}ingestion-issues/{instance.pk}/"
|
|
761
|
+
|
|
762
|
+
@classmethod
|
|
763
|
+
def setUpTestData(cls):
|
|
764
|
+
# Create sources for the snapshots
|
|
765
|
+
sources = (
|
|
766
|
+
IPFabricSource.objects.create(
|
|
767
|
+
name="Issue Test Source A",
|
|
768
|
+
url="https://issue-a.local",
|
|
769
|
+
parameters={"auth": "token", "verify": True},
|
|
770
|
+
last_synced=timezone.now(),
|
|
771
|
+
),
|
|
772
|
+
IPFabricSource.objects.create(
|
|
773
|
+
name="Issue Test Source B",
|
|
774
|
+
url="https://issue-b.local",
|
|
775
|
+
parameters={"auth": "token", "verify": False},
|
|
776
|
+
last_synced=timezone.now(),
|
|
777
|
+
),
|
|
778
|
+
IPFabricSource.objects.create(
|
|
779
|
+
name="Issue Test Source C",
|
|
780
|
+
url="https://issue-c.local",
|
|
781
|
+
parameters={"auth": "token", "verify": True},
|
|
782
|
+
last_synced=timezone.now(),
|
|
783
|
+
),
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
# Create snapshots for the syncs
|
|
787
|
+
snapshots = (
|
|
788
|
+
IPFabricSnapshot.objects.create(
|
|
789
|
+
name="Issue Test Snapshot A",
|
|
790
|
+
source=sources[0],
|
|
791
|
+
snapshot_id="issue-snap-a",
|
|
792
|
+
status="loaded",
|
|
793
|
+
data={"sites": ["IssueTestSiteA", "IssueTestSiteB"]},
|
|
794
|
+
date=timezone.now(),
|
|
795
|
+
last_updated=timezone.now(),
|
|
796
|
+
),
|
|
797
|
+
IPFabricSnapshot.objects.create(
|
|
798
|
+
name="Issue Test Snapshot B",
|
|
799
|
+
source=sources[1],
|
|
800
|
+
snapshot_id="issue-snap-b",
|
|
801
|
+
status="loaded",
|
|
802
|
+
data={"devices": ["IssueTestDevice1", "IssueTestDevice2"]},
|
|
803
|
+
date=timezone.now(),
|
|
804
|
+
last_updated=timezone.now(),
|
|
805
|
+
),
|
|
806
|
+
IPFabricSnapshot.objects.create(
|
|
807
|
+
name="Issue Test Snapshot C",
|
|
808
|
+
source=sources[2],
|
|
809
|
+
snapshot_id="issue-snap-c",
|
|
810
|
+
status="unloaded",
|
|
811
|
+
data={"interfaces": ["IssueTestInterface1"]},
|
|
812
|
+
date=timezone.now(),
|
|
813
|
+
last_updated=timezone.now(),
|
|
814
|
+
),
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
# Create syncs for the ingestions
|
|
818
|
+
syncs = (
|
|
819
|
+
IPFabricSync.objects.create(
|
|
820
|
+
name="Issue Test Sync A",
|
|
821
|
+
snapshot_data=snapshots[0],
|
|
822
|
+
parameters={"site": True, "device": False},
|
|
823
|
+
),
|
|
824
|
+
IPFabricSync.objects.create(
|
|
825
|
+
name="Issue Test Sync B",
|
|
826
|
+
snapshot_data=snapshots[1],
|
|
827
|
+
parameters={"device": True, "interface": True},
|
|
828
|
+
),
|
|
829
|
+
IPFabricSync.objects.create(
|
|
830
|
+
name="Issue Test Sync C",
|
|
831
|
+
snapshot_data=snapshots[2],
|
|
832
|
+
parameters={"ipaddress": True, "prefix": False},
|
|
833
|
+
),
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
# Create ingestions for the issues
|
|
837
|
+
ingestions = (
|
|
838
|
+
IPFabricIngestion.objects.create(sync=syncs[0]),
|
|
839
|
+
IPFabricIngestion.objects.create(sync=syncs[1]),
|
|
840
|
+
IPFabricIngestion.objects.create(sync=syncs[2]),
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
# Create ingestion issues for testing
|
|
844
|
+
IPFabricIngestionIssue.objects.create(
|
|
845
|
+
ingestion=ingestions[0],
|
|
846
|
+
model="dcim.site",
|
|
847
|
+
message="Failed to create site due to validation error",
|
|
848
|
+
raw_data='{"name": "Invalid Site", "slug": ""}',
|
|
849
|
+
coalesce_fields="name,slug",
|
|
850
|
+
defaults="{}",
|
|
851
|
+
exception="ValidationError: Slug field cannot be empty",
|
|
852
|
+
)
|
|
853
|
+
IPFabricIngestionIssue.objects.create(
|
|
854
|
+
ingestion=ingestions[0],
|
|
855
|
+
model="dcim.device",
|
|
856
|
+
message="Device type not found",
|
|
857
|
+
raw_data='{"hostname": "test-device", "device_type": "NonExistentType"}',
|
|
858
|
+
coalesce_fields="hostname",
|
|
859
|
+
defaults='{"status": "active"}',
|
|
860
|
+
exception="DoesNotExist: DeviceType matching query does not exist",
|
|
861
|
+
)
|
|
862
|
+
IPFabricIngestionIssue.objects.create(
|
|
863
|
+
ingestion=ingestions[1],
|
|
864
|
+
model="dcim.interface",
|
|
865
|
+
message="Interface creation failed - invalid MAC address",
|
|
866
|
+
raw_data='{"name": "eth0", "mac_address": "invalid-mac", "device": 1}',
|
|
867
|
+
coalesce_fields="name,device",
|
|
868
|
+
defaults='{"type": "1000base-t"}',
|
|
869
|
+
exception="ValidationError: Enter a valid MAC address",
|
|
870
|
+
)
|
|
871
|
+
IPFabricIngestionIssue.objects.create(
|
|
872
|
+
ingestion=ingestions[2],
|
|
873
|
+
model="ipam.ipaddress",
|
|
874
|
+
message="IP address already exists",
|
|
875
|
+
raw_data='{"address": "192.168.1.1/24", "status": "active"}',
|
|
876
|
+
coalesce_fields="address",
|
|
877
|
+
defaults='{"dns_name": ""}',
|
|
878
|
+
exception="IntegrityError: IP address 192.168.1.1/24 already exists",
|
|
879
|
+
)
|