-
Notifications
You must be signed in to change notification settings - Fork 266
/
acitoolkit.py
executable file
·7339 lines (6260 loc) · 270 KB
/
acitoolkit.py
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
###############################################################################
# _ ____ ___ #
# / \ / ___|_ _| #
# / _ \| | | | #
# / ___ \ |___ | | #
# _____/_/ \_\____|___|_ _ #
# |_ _|__ ___ | | | _(_) |_ #
# | |/ _ \ / _ \| | |/ / | __| #
# | | (_) | (_) | | <| | |_ #
# |_|\___/ \___/|_|_|\_\_|\__| #
# #
###############################################################################
# #
# Copyright (c) 2015 Cisco Systems #
# All Rights Reserved. #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT#
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the #
# License for the specific language governing permissions and limitations #
# under the License. #
# #
###############################################################################
""" Main ACI Toolkit module
This is the main module that comprises the ACI Toolkit.
"""
from collections import Sequence
import logging
from operator import attrgetter, itemgetter
import re
import sys
import copy
from requests.compat import urlencode
from .acibaseobject import BaseACIObject, BaseInterface, _Tag
from .aciphysobject import Interface, Fabric
from .acisession import Session
from .aciTable import Table
from .acicounters import InterfaceStats
from .acitoolkitlib import Credentials
class Tenant(BaseACIObject):
"""
The Tenant class is used to represent the tenants within the acitoolkit
object model. In the APIC model, this class is roughly equivalent to
the fvTenant class.
"""
def __init__(self, name, parent=None):
"""
:param name: String containing the Tenant name
:param parent: None or An instance of Fabric class representing the Pod
which contains this Tenant.
"""
if parent is not None and not isinstance(parent, Fabric) and not isinstance(parent, LogicalModel):
raise TypeError('Parent must be None or an instance of Fabric class. Parent given as %s' % type(parent))
super(Tenant, self).__init__(name, parent)
@classmethod
def _get_apic_classes(cls):
"""
Get the APIC classes used by this acitoolkit class.
:returns: list of strings containing APIC class names
"""
return ['fvTenant']
@staticmethod
def _get_parent_class():
"""
Gets the class of the parent object
:returns: class of parent object
"""
return LogicalModel
def _get_instance_subscription_urls(self):
url = '/api/mo/uni/tn-{}.json?subscription=yes'.format(self.name)
return [url]
@staticmethod
def _get_name_dn_delimiters():
return ['/tn-', '/']
def get_json(self):
"""
Returns json representation of the fvTenant object
:returns: A json dictionary of fvTenant
"""
attr = self._generate_attributes()
return super(Tenant, self).get_json(self._get_apic_classes()[0],
attributes=attr)
def push_to_apic(self, session):
"""
Push the appropriate configuration to the APIC for this Tenant.
All of the subobject configuration will also be pushed.
:param session: the instance of Session used for APIC communication
:returns: Requests Response code
"""
resp = session.push_to_apic(self.get_url(),
self.get_json())
return resp
@classmethod
def _get_toolkit_to_apic_classmap(cls):
"""
Gets the APIC class to an acitoolkit class mapping dictionary
:returns: dict of APIC class names to acitoolkit classes
"""
return {'fvAp': AppProfile,
'fvBD': BridgeDomain,
'vzCPIf': ContractInterface,
'fvCtx': Context,
'vzBrCP': Contract,
'vzFilter': Filter,
'vzTaboo': Taboo,
'l3extOut': OutsideL3}
@classmethod
def get_deep(cls, session, names=(), limit_to=(), subtree='full', config_only=False, parent=None):
"""
Get the Tenant objects and all of the children objects.
:param session: the instance of Session used for APIC communication
:param names: list of strings containing the tenant names. If no list is given, all tenants will be collected.
It should be noted that if relations extend across tenants, the relation will only be
populated if the tenants are included in this list.
:param limit_to: list of strings containing the APIC classes to limit the collection to i.e. ['fvTenant',
'fvBD']. If no list is given, all classes will be collected.
:param subtree: String containing the rsp-subtree option. Default is 'full'.
:param config_only: Boolean containing whether to collect only configurable parameters
:param parent: The parent instance to assign to the tenant objects. If None, a Fabric instance will be created.
:returns: Requests Response code
"""
resp = []
if isinstance(names, str) or \
not isinstance(names, Sequence) or \
not all(isinstance(name, str) for name in names):
raise TypeError('names should be a Sequence of strings')
names = list(names) or [tenant.name for tenant in Tenant.get(session)]
if isinstance(limit_to, str) or \
not isinstance(limit_to, Sequence) or \
not all(isinstance(class_name, str) for class_name in limit_to):
raise TypeError('limit_to should be a Sequence of strings')
limit_to = list(limit_to)
if 'common' in names:
# If tenant common is part of the list, put it at the front so we populate that first
names.remove('common')
names.insert(0, 'common')
params = {'query-target': 'self', 'rsp-subtree': subtree}
if len(limit_to):
params['rsp-subtree-class'] = ','.join(limit_to)
if config_only:
params['rsp-prop-include'] = 'config-only'
query = urlencode(params)
objs = []
full_data = []
if parent is None:
parent = Fabric()
for name in names:
query_url = '/api/mo/uni/tn-{}.json?{}'.format(name, query)
ret = session.get(query_url)
# the following works around a bug encountered in the json returned from the APIC
# Python3 throws an error 'TypeError: 'str' does not support the buffer interface'
# This error gets catched and the replace is done with byte code in a Python3 compatible way
try:
ret._content = ret._content.replace("\\\'", "'")
except TypeError:
ret._content = ret._content.replace(b"\\\'", b"'")
data = ret.json()['imdata']
if len(data):
full_data.append(data[0])
obj = super(Tenant, cls).get_deep(full_data=data,
working_data=data,
parent=parent,
limit_to=limit_to,
subtree=subtree,
config_only=config_only)
if obj is not None:
objs.append(obj)
resp.append(obj)
else:
print(name, 'resulted in a null object')
obj_dict = build_object_dictionary(objs)
for obj in objs:
obj._extract_relationships(full_data, obj_dict)
return resp
@classmethod
def get(cls, session, parent=None):
"""
Gets all of the tenants from the APIC.
:param parent: Parent object of the Tenant
:param session: the instance of Session used for APIC communication
:returns: a list of Tenant objects
"""
tenants = BaseACIObject.get(session, cls, cls._get_apic_classes()[0])
if parent:
if isinstance(parent, LogicalModel):
for tenant in tenants:
parent.add_child(tenant)
return tenants
@classmethod
def exists(cls, session, tenant):
"""
Check if a tenant exists on the APIC.
:param session: the instance of Session used for APIC communication
:param tenant: the instance of Tenant to check if exists on the APIC
:returns: True or False
"""
apic_tenants = cls.get(session)
return any(apic_tenant == tenant for apic_tenant in apic_tenants)
@staticmethod
def get_url(fmt='json'):
"""
Get the URL used to push the configuration to the APIC
if no format parameter is specified, the format will be 'json'
otherwise it will return '/api/mo/uni.' with the format string
appended.
:param fmt: optional format string, default is 'json'
:returns: URL string
"""
return '/api/mo/uni.' + fmt
@staticmethod
def get_table(tenants, title=''):
"""
Will create table of switch context information
:param title:
:param tenants:
"""
headers = ['Tenant', 'Description']
data = []
for tenant in sorted(tenants):
data.append([
tenant.name,
tenant.descr])
data = sorted(data)
table = Table(data, headers, title=title + 'Tenant')
return [table, ]
class AppProfile(BaseACIObject):
"""
The AppProfile class is used to represent the Application Profiles within
the acitoolkit object model. In the APIC model, this class is roughly
equivalent to the fvAp class.
"""
def __init__(self, name, parent):
"""
:param name: String containing the Application Profile name
:param parent: An instance of Tenant class representing the Tenant\
which contains this Application Profile.
"""
if not isinstance(parent, Tenant):
raise TypeError('Parent must be of Tenant class')
super(AppProfile, self).__init__(name, parent)
@classmethod
def _get_apic_classes(cls):
"""
Get the APIC classes used by this acitoolkit class.
:returns: list of strings containing APIC class names
"""
return ['fvAp']
@classmethod
def _get_toolkit_to_apic_classmap(cls):
"""
Gets the APIC class to an acitoolkit class mapping dictionary
:returns: dict of APIC class names to acitoolkit classes
"""
return {'fvAEPg': EPG, }
@staticmethod
def _get_parent_class():
"""
Gets the class of the parent object
:returns: class of parent object
"""
return Tenant
@staticmethod
def _get_name_dn_delimiters():
return ['/ap-', '/']
def _get_instance_subscription_urls(self):
url = '/api/mo/uni/tn-%s/ap-%s.json?subscription=yes' % (self._parent.name, self.name)
return [url]
@classmethod
def _get_name_from_dn(cls, dn):
if '/LDevInst-' in dn or '/lDev-' in dn:
return 'ServiceGraph'
elif '/ap-' not in dn:
return None
name = dn.split('/ap-')[1].split('/')[0]
return name
def get_json(self):
"""
Returns json representation of the AppProfile object.
:returns: json dictionary of fvAp
"""
attr = self._generate_attributes()
return super(AppProfile, self).get_json(self._get_apic_classes()[0],
attributes=attr)
@classmethod
def get(cls, session, tenant):
"""Gets all of the Application Profiles from the APIC.
:param session: the instance of Session used for APIC communication
:param tenant: the instance of Tenant used to limit the Application\
Profiles retreived from the APIC
:returns: List of AppProfile objects
"""
return BaseACIObject.get(session, cls, cls._get_apic_classes()[0],
parent=tenant, tenant=tenant)
def _get_url_extension(self):
return '/ap-%s' % self.name
@staticmethod
def get_table(app_profiles, title=''):
"""
Will create table of app_profile information for a given tenant
:param title:
:param app_profiles:
"""
result = []
headers = ['Tenant', 'App Profile', 'Description',
'EPGs']
by_name = attrgetter('name')
for app_profile in sorted(app_profiles, key=by_name):
data = []
for epg in sorted(app_profile.get_children(EPG), key=by_name):
data.append([
app_profile.get_parent().name,
app_profile.name,
app_profile.descr,
epg.name,
])
result.append(Table(data, headers, title=title + 'Application Profile: {0}'.format(app_profile.name)))
return result
class L2Interface(BaseACIObject):
""" The L2Interface class creates an logical L2 interface that can be\
attached to a physical interface. This interface defines the L2\
encapsulation i.e. VLAN, VXLAN, or NVGRE
"""
def __init__(self, name, encap_type, encap_id, encap_mode=None):
"""
:param name: String containing the L2Interface instance name
:param encap_type: String containing the encapsulation type.\
Valid values are 'VLAN', 'VXLAN', or 'NVGRE'.
:param encap_id: String containing the encapsulation specific\
identifier representing the virtual L2 network (i.e. for VXLAN,\
this is the numeric value of the VNID).
"""
super(L2Interface, self).__init__(name)
if encap_type not in ('vlan', 'vxlan', 'nvgre'):
raise ValueError("Encap type must be one of 'vlan',"
" 'vxlan', or 'nvgre'")
self.encap_type = encap_type
self.encap_id = encap_id
self.encap_mode = encap_mode
def is_interface(self):
"""
Returns whether this instance is considered an interface.
:returns: True
"""
return True
def get_encap_type(self):
"""
Get the encap_type of the L2 interface.
Valid values are 'vlan', 'vxlan', and 'nvgre'
:returns: String containing encap_type value.
"""
return self.encap_type
def get_encap_id(self):
"""
Get the encap_id of the L2 interface.
The value is returned as a string and depends on the encap_type
(i.e. VLAN VID, VXLAN VNID, or NVGRE VSID)
:returns: String containing encapsulation identifier value.
"""
return self.encap_id
def _get_path(self):
"""
Get the path of this interface used when communicating with\
the APIC object model.
:returns: String containing the path
"""
for relation in self._relations:
if relation.item.is_interface():
return relation.item._get_path()
@staticmethod
def parse_encap(encap):
"""
Parses the encap_type and encap_id from a json encap string
Examples: vlan-515 / vxlan-5000
:param encap: String containing the json encap format
:returns: encap_type, encap_id
"""
encap_type, encap_id = encap.split('-')
return encap_type, encap_id
class CommonEPG(BaseACIObject):
"""
Base class for EPG and OutsideEPG.
Not meant to be instantiated directly
"""
def __init__(self, epg_name, parent=None):
"""
:param epg_name: String containing the name of this EPG
:param parent: Instance of the AppProfile class representing\
the Application Profile where this EPG is contained.
"""
super(CommonEPG, self).__init__(epg_name, parent)
# Contract references
def provide(self, contract):
"""
Make this EPG provide a Contract
:param contract: Instance of Contract class to be provided by this EPG.
:returns: True
"""
if self.does_provide(contract):
return True
self._add_relation(contract, 'provided')
return True
def does_provide(self, contract):
"""
Check if this EPG provides a specific Contract.
:param contract: Instance of Contract class to check if it is\
provided by this EPG.
:returns: True or False. True if the EPG does provide the Contract.
"""
return self._has_relation(contract, 'provided')
def dont_provide(self, contract):
"""
Make this EPG not provide a Contract
:param contract: Instance of Contract class to be no longer provided\
by this EPG.
:returns: True
"""
self._remove_relation(contract, 'provided')
def get_all_provided(self, deleted=False):
"""
Get all of the Contracts provided by this EPG
:param deleted: Boolean indicating whether to get Contracts that are provided
or that the provided was marked as deleted
:returns: List of Contract objects that are provided by the EPG.
"""
if deleted:
return self._get_all_detached_relation(Contract, 'provided')
else:
return self._get_all_relation(Contract, 'provided')
def consume(self, contract):
"""
Make this EPG consume a Contract
:param contract: Contract class instance to be consumed by this EPG.
:returns: True
"""
if self.does_consume(contract):
return True
self._add_relation(contract, 'consumed')
return True
def does_consume(self, contract):
"""
Check if this EPG consumes a specific Contract
:param contract: Instance of Contract class to check if it is\
consumed by this EPG.
:returns: True or False. True if the EPG does consume the Contract.
"""
return self._has_relation(contract, 'consumed')
def dont_consume(self, contract):
"""
Make this EPG not consume a Contract. It does not check to see
if the Contract was already consumed
:param contract: Instance of Contract class to be no longer consumed\
by this EPG.
:returns: True
"""
self._remove_relation(contract, 'consumed')
return True
def get_all_consumed(self, deleted=False):
"""
Get all of the Contracts consumed by this EPG
:param deleted: Boolean indicating whether to get Contracts that are consumed
or that the consumed was marked as deleted
:returns: List of Contract objects that are consumed by the EPG.
"""
if deleted:
return self._get_all_detached_relation(Contract, 'consumed')
else:
return self._get_all_relation(Contract, 'consumed')
def consume_cif(self, contract_interface):
"""
Make this EPG consume a ContractInterface
:param contract_interface: ContractInterface class instance to be consumed by this EPG.
:returns: True
"""
if self.does_consume_cif(contract_interface):
return True
self._add_relation(contract_interface, 'consumed')
return True
def does_consume_cif(self, contract_interface):
"""
Check if this EPG consumes a specific Contract
:param contract_interface:
:returns: True or False. True if the EPG does consume the ContractInterface.
"""
return self._has_relation(contract_interface, 'consumed')
def dont_consume_cif(self, contract_interface):
"""
Make this EPG not consume a ContractInterface. It does not check to see
if the ContractInterface was already consumed
:param contract_interface:
:returns: True
"""
self._remove_relation(contract_interface, 'consumed')
return True
def get_all_consumed_cif(self, deleted=False):
"""
Get all of the ContractInterfaces consumed by this EPG
:param deleted: Boolean indicating whether to get ContractInterfaces that
are consumed or that the consumed was marked as deleted
:returns: List of ContractInterface objects that are consumed by the EPG.
"""
if deleted:
return self._get_all_detached_relation(ContractInterface, 'consumed')
else:
return self._get_all_relation(ContractInterface, 'consumed')
def protect(self, taboo):
"""
Make this EPG protected by a Taboo
:param taboo: Instance of Taboo class to protect this EPG.
:returns: True
"""
if self.does_protect(taboo):
return True
self._add_relation(taboo, 'protected')
return True
def does_protect(self, taboo):
"""
Check if this EPG is protected by a specific Taboo.
:param taboo: Instance of Taboo class to check if it protects
this EPG.
:returns: True or False. True if the EPG is protected by the Taboo.
"""
return self._has_relation(taboo, 'protected')
def dont_protect(self, taboo):
"""
Make this EPG not protected by a Taboo
:param taboo: Instance of Taboo class to no longer protect\
this EPG.
:returns: True
"""
self._remove_relation(taboo, 'protected')
def get_all_protected(self, deleted=False):
"""
Get all of the Taboos protecting this EPG
:param deleted: Boolean indicating whether to get Taboos that are protected
or that the protected was marked as deleted
:returns: List of Taboo objects that are protecting the EPG.
"""
if deleted:
return self._get_all_detached_relation(Taboo, 'protected')
else:
return self._get_all_relation(Taboo, 'protected')
def get_interfaces(self, status='attached'):
"""
Get all of the interfaces that this EPG is attached.
The default is to get list of 'attached' interfaces.
If 'status' is set to 'detached' it will return the list of
detached Interface objects (Those EPGs which are no longer
attached to an Interface, but the configuration is not yet
pushed to the APIC.)
:param status: 'attached' or 'detached'. Defaults to 'attached'.
:returns: List of Interface objects
"""
resp = []
for relation in self._relations:
if relation.item.is_interface() and relation.status == status:
resp.append(relation.item)
return resp
def _get_common_json(self):
"""Internal routine to generate JSON common to EPGs and Outside EPGs"""
children = []
for contract in self.get_all_provided():
text = {'fvRsProv': {'attributes': {'tnVzBrCPName': contract.name}}}
children.append(text)
for contract in self.get_all_consumed():
text = {'fvRsCons': {'attributes': {'tnVzBrCPName': contract.name}}}
children.append(text)
for contract_interface in self.get_all_consumed_cif():
text = {'fvRsConsIf': {'attributes': {'tnVzCPIfName': contract_interface.name}}}
children.append(text)
for taboo in self.get_all_protected():
text = {'fvRsProtBy': {'attributes': {'tnVzTabooName': taboo.name}}}
children.append(text)
for contract in self.get_all_provided(deleted=True):
text = {'fvRsProv': {'attributes': {'status': 'deleted', 'tnVzBrCPName': contract.name}}}
children.append(text)
for contract in self.get_all_consumed(deleted=True):
text = {'fvRsCons': {'attributes': {'status': 'deleted', 'tnVzBrCPName': contract.name}}}
children.append(text)
for contract_interface in self.get_all_consumed_cif(deleted=True):
text = {'fvRsConsIf': {'attributes': {'status': 'deleted', 'tnVzCPIfName': contract_interface.name}}}
children.append(text)
for taboo in self.get_all_protected(deleted=True):
text = {'fvRsProtBy': {'attributes': {'status': 'deleted', 'tnVzTabooName': taboo.name}}}
children.append(text)
return children
@classmethod
def get(cls, session, parent=None, tenant=None):
"""Gets all of the EPGs from the APIC.
:param session: the instance of Session used for APIC communication
:param parent: Instance of the AppProfile class used to limit the EPGs\
retreived from the APIC.
:param tenant: Instance of Tenant class used to limit the EPGs\
retreived from the APIC.
:returns: List of CommonEPG instances (or EPG instances if called\
from EPG class)
"""
return BaseACIObject.get(session, cls, cls._get_apic_classes()[0],
parent, tenant)
class AttributeCriterion(BaseACIObject):
""" AttributeCriterion : roughly equivalent to fvCrtrn """
def __init__(self, name, parent=None):
"""
Initializes the AttributeCriterion with a name and optionally an EPG parent
:param name: String containing the AttributeCriterion name
:param parent: Instance of the EPG class representing where this AttributeCriterion is defined
:return: Instance of AttributeCriterion class
"""
if parent:
if not isinstance(parent, EPG):
raise TypeError('Parent must be instance of EPG')
parent.is_attributed_based = True
super(AttributeCriterion, self).__init__(name, parent)
self._match = 'any'
self._ip_addresses = []
@staticmethod
def _get_parent_class():
"""
Gets the class of the parent object
:returns: class of parent object
"""
return EPG
def _get_instance_subscription_urls(self):
url = '/api/mo/uni/tn-%s/ap-%s/epg-%s/crtrn.json?subscription=yes' % (
self._parent._parent._parent.name, self._parent._parent.name, self._parent.name)
return [url]
@staticmethod
def _get_name_dn_delimiters():
return ['/crtrn', '/']
@property
def match(self):
"""
Return the match property
:return: String containing the match property. Possible values are 'any' or 'all'
"""
return self._match
@match.setter
def match(self, x):
"""
Set the match property
:param x: String containing the match property. Possible values are 'any' or 'all'
:return: None
"""
assert x in ['any', 'all']
self._match = x
@classmethod
def _get_apic_classes(cls):
"""
Get the APIC classes used by this acitoolkit class.
:returns: list of strings containing APIC class names
"""
return ['fvCrtrn']
def _generate_attributes(self):
attr = super(AttributeCriterion, self)._generate_attributes()
attr['match'] = self.match
return attr
def add_ip_address(self, ip_addr):
"""
Add an IP address as an attribute
:param ip_addr: String containing the IP address
:return: None
"""
if ip_addr not in self._ip_addresses:
self._ip_addresses.append(ip_addr)
def get_ip_addresses(self):
"""
return the list of IP addresses
"""
return self._ip_addresses
@classmethod
def get_deep(cls, full_data, working_data, parent=None, limit_to=(), subtree='full', config_only=False):
attr_crtrn_data = working_data[0]['fvCrtrn']
attr_ctrn = AttributeCriterion(str(attr_crtrn_data['attributes']['name']), parent)
attr_ctrn._populate_from_attributes(attr_crtrn_data['attributes'])
for child in attr_crtrn_data.get('children', ()):
if 'fvIpAttr' in child:
attr_ctrn.add_ip_address(str(child['fvIpAttr']['attributes']['ip']))
def get_json(self):
"""
Returns JSON representation of the AttributeCriterion
:return:
"""
attr = self._generate_attributes()
children = []
for ip_address in self._ip_addresses:
child = {'fvIpAttr': {'attributes': {'name': ip_address.split('/')[0],
'ip': ip_address},
'children': []}}
children.append(child)
return super(AttributeCriterion, self).get_json(self._get_apic_classes()[0],
attributes=attr,
children=children)
class EPG(CommonEPG):
""" EPG : roughly equivalent to fvAEPg """
def __init__(self, epg_name, parent=None):
"""
Initializes the EPG with a name and, optionally,
an AppProfile parent.
If the parent is specified and is not an AppProfile,
an error will occur.
:param epg_name: String containing the name of the EPG.
:param parent: Instance of the AppProfile class representing\
the Application Profile where this EPG is contained.
"""
if parent:
if not isinstance(parent, AppProfile):
raise TypeError('Parent must be instance of AppProfile')
super(EPG, self).__init__(epg_name, parent)
self._leaf_bindings = []
self.match_type = None
self.class_id = None
self.scope = None
self._deployment_immediacy = None
self._intra_epg_isolation = False
self._dom_deployment_immediacy = None
self._dom_resolution_immediacy = None
self._is_attribute_based = False
self._base_epg = None
def _generate_attributes(self):
attributes = super(EPG, self)._generate_attributes()
if self._is_attribute_based:
attributes['isAttrBasedEPg'] = 'yes'
if self._intra_epg_isolation:
attributes['pcEnfPref'] = 'enforced'
return attributes
@property
def is_attributed_based(self):
"""
Get whether the EPG is attribute based
:return: True if attribute based. False otherwise.
"""
return self._is_attribute_based
@is_attributed_based.setter
def is_attributed_based(self, x):
"""
Set the attribute_based flag. Indicates that the EPG is attribute based.
:param x: String containing 'true' or 'yes' indicates that the EPG is attribute based.
:return: None
"""
if isinstance(x, str):
if x.lower() in ['true', 'yes']:
self._is_attribute_based = True
else:
self._is_attribute_based = False
self._is_attribute_based = x
def set_base_epg(self, epg):
"""
Sets the Base EPG. Used by Attribute-based EPGs to indicate that the BridgeDomain, NodeAttach, and
PathAttach relations should be copied from the base EPG when generating JSON.
:param epg: EPG class instance of the Base EPG
:return: None
"""
self._base_epg = epg
@classmethod
def _get_apic_classes(cls):
"""
Get the APIC classes used by this acitoolkit class.
:returns: list of strings containing APIC class names
"""
return ['fvAEPg']
@classmethod
def _get_toolkit_to_apic_classmap(cls):
"""
Gets the APIC class to an acitoolkit class mapping dictionary
:returns: dict of APIC class names to acitoolkit classes
"""
return {'fvCEp': Endpoint,
'fvStCEp': Endpoint,
'fvCrtrn': AttributeCriterion}
@staticmethod
def _get_parent_class():
"""
Gets the class of the parent object
:returns: class of parent object
"""
return AppProfile
def _get_instance_subscription_urls(self):
url = '/api/mo/uni/tn-%s/ap-%s/epg-%s.json?subscription=yes' % (
self._parent._parent.name, self._parent.name, self.name)
return [url]
@staticmethod
def _get_name_dn_delimiters():
return ['/epg-', '/']
@classmethod
def _get_name_from_dn(cls, dn):
if '/LDevInst-' in dn or '/lDev-' in dn:
return 'ServiceGraph'
elif '/epg-' not in dn:
return None
return dn.split('/epg-')[1].split('/')[0]
def _populate_from_attributes(self, attributes):
"""
Sets the attributes when creating objects from the APIC.
Called from the base object when calling the classmethod get()
"""
super(EPG, self)._populate_from_attributes(attributes)
if 'matchT' in attributes:
self.match_type = str(attributes.get('matchT'))
if 'pcTag' in attributes:
self.class_id = str(attributes.get('pcTag'))
if 'scope' in attributes:
self.scope = str(attributes.get('scope'))
if 'name' in attributes:
self.name = str(attributes.get('name'))
elif self.dn != '':
self.name = self._get_name_from_dn(self.dn)
if str(attributes.get('isAttrBasedEPg')).lower() in ['true', 'yes']:
self._is_attribute_based = True
else:
self._is_attribute_based = False
if attributes.get('pcEnfPref') == 'enforced':
self._intra_epg_isolation = True
else:
self._intra_epg_isolation = False
# Infrastructure Domain references
def add_infradomain(self, infradomain):
"""
Add Infrastructure Domain to the EPG
:param infradomain: Instance of InfraDomain class.
"""
if not isinstance(infradomain, EPGDomain):
raise TypeError('add_infradomain not called with InfraDomain')
self.populate_children(True)
if self.has_child(infradomain):
return
self.add_child(infradomain)
infradomain._add_relation(self)
# Bridge Domain references
def add_bd(self, bridgedomain):
"""
Add BridgeDomain to the EPG, roughly equivalent to fvRsBd
:param bridgedomain: Instance of BridgeDomain class. Represents\
the BridgeDomain that this EPG will be assigned.\
An EPG can only be assigned to a single\
BridgeDomain.
"""
if not isinstance(bridgedomain, BridgeDomain):
raise TypeError('add_bd not called with BridgeDomain')
self._remove_all_relation(BridgeDomain)
self._add_relation(bridgedomain)
def remove_bd(self):
"""
Remove BridgeDomain from the EPG.