diff --git a/cloudvision/cvlib/tags.py b/cloudvision/cvlib/tags.py index 62b0577b..e26a36f6 100644 --- a/cloudvision/cvlib/tags.py +++ b/cloudvision/cvlib/tags.py @@ -2,20 +2,24 @@ # Use of this source code is governed by the Apache License 2.0 # that can be found in the COPYING file. -from typing import Dict +from grpc import RpcError +from typing import Dict, List, Tuple from arista.tag.v2.services import ( TagAssignmentServiceStub, TagAssignmentStreamRequest, TagConfigServiceStub, TagConfigSetRequest, + TagConfigSetSomeRequest, TagAssignmentConfigStreamRequest, TagAssignmentConfigServiceStub, - TagAssignmentConfigSetRequest + TagAssignmentConfigSetRequest, + TagAssignmentConfigSetSomeRequest ) from arista.tag.v2.tag_pb2 import ( TagAssignment, TagAssignmentConfig, + TagConfig, ELEMENT_TYPE_DEVICE, ELEMENT_TYPE_INTERFACE, CREATOR_TYPE_USER @@ -78,8 +82,8 @@ class Tags: def __init__(self, context): self.ctx = context - self.relevantTagAssigns: Dict = {} - self.relevantIntfTagAssigns: Dict = {} + self.relevantTagAssigns: Dict = None + self.relevantIntfTagAssigns: Dict = None def _getTagUpdatesFromWorkspace(self, etype=ELEMENT_TYPE_DEVICE): ''' @@ -125,6 +129,98 @@ def _createTag(self, etype: int, label: str, value: str): tagClient = self.ctx.getApiClient(TagConfigServiceStub) tagClient.Set(setRequest) + def _createTags(self, etype: int, tags: List[Tuple], configsPerReq=1000): + ''' + _createTags creates multiple tags if they don't already exist, + of the specified ElementType, in the workspace. + The input tags is a List of Tuples of (label, value) + ''' + # remove from the list if the tag already exists + for (label, value) in tags[:]: + if not label or not value: + raise TagOperationException(label, value, 'create') + if etype == ELEMENT_TYPE_DEVICE and self._deviceTagExists(label, value): + tags.remove((label, value)) + if etype == ELEMENT_TYPE_INTERFACE and self._interfaceTagExists(label, value): + tags.remove((label, value)) + if not tags: + return + # create the tags + tagConfigs = [] + wsID = self.ctx.getWorkspaceId() + for (label, value) in tags: + tagConfig = TagConfig() + tagConfig.key.workspace_id.value = wsID + tagConfig.key.element_type = etype + tagConfig.key.label.value = label + tagConfig.key.value.value = value + tagConfigs.append(tagConfig) + res = [] + # chunk each SetSome request to a maximum of configsPerReq + for start in range(0, len(tagConfigs), configsPerReq): + setSomeRequest = TagConfigSetSomeRequest( + values=tagConfigs[start:start + configsPerReq] + ) + tagClient = self.ctx.getApiClient(TagConfigServiceStub) + try: + for res in tagClient.SetSome(setSomeRequest): + pass + except RpcError: + raise TagOperationException('', '', 'create') + + def _assignTagSet(self, etype: int, tagAssign: Tuple, remove: bool = False): + ''' + _assignTagSet assigns a tag. + The input tagAssign is a tuple of the form: + (deviceId, interfaceId, label, value) + If remove is True, then unassigns the tag. + ''' + (deviceId, interfaceId, label, value) = tagAssign + setRequest = TagAssignmentConfigSetRequest() + wsID = self.ctx.getWorkspaceId() + setRequest.value.key.workspace_id.value = wsID + setRequest.value.key.element_type = etype + setRequest.value.key.label.value = label + setRequest.value.key.value.value = value + setRequest.value.key.device_id.value = deviceId + setRequest.value.key.interface_id.value = interfaceId + setRequest.value.remove.value = remove + tagClient = self.ctx.getApiClient(TagAssignmentConfigServiceStub) + tagClient.Set(setRequest) + + def _assignTagsSets(self, etype: int, tagAssigns: List[Tuple], + remove: bool = False, configsPerReq: int = 1000): + ''' + _assignTagsSets assigns/unassigns multiple tags. + The input tagAssigns is a List of Tuples of the form: + (deviceId, interfaceId, label, value) + If remove is True, then unassigns the tags. + ''' + tagAssConfigs = [] + wsID = self.ctx.getWorkspaceId() + for (deviceId, interfaceId, label, value) in tagAssigns: + tagAssConfig = TagAssignmentConfig() + tagAssConfig.key.workspace_id.value = wsID + tagAssConfig.key.element_type = etype + tagAssConfig.key.label.value = label + tagAssConfig.key.value.value = value + tagAssConfig.key.device_id.value = deviceId + tagAssConfig.key.interface_id.value = interfaceId + tagAssConfig.remove.value = remove + tagAssConfigs.append(tagAssConfig) + res = [] + # chunk each SetSome request to a maximum of configsPerReq + for start in range(0, len(tagAssConfigs), configsPerReq): + setSomeRequest = TagAssignmentConfigSetSomeRequest( + values=tagAssConfigs[start:start + configsPerReq] + ) + tagClient = self.ctx.getApiClient(TagAssignmentConfigServiceStub) + try: + for res in tagClient.SetSome(setSomeRequest): + pass + except RpcError: + raise TagOperationException('', '', 'assign') + # The following are methods for device Tags def _deviceTagExists(self, label: str, value: str): @@ -196,29 +292,6 @@ def _getAllDeviceTagsFromMainline(self): self.relevantTagAssigns[deviceId][label].append(value) return self.relevantTagAssigns - def _assignDeviceTagSet(self, deviceId: str, label: str, value: str): - ''' - _assignDeviceTagSet assigns a device tag if it isn't already assigned - ''' - # check if the tag is already assigned to this device - if self._deviceTagAssigned(deviceId, label, value): - return - # create the tag - self._createTag(ELEMENT_TYPE_DEVICE, label, value) - # assign the tag - setRequest = TagAssignmentConfigSetRequest() - wsID = self.ctx.getWorkspaceId() - setRequest.value.key.workspace_id.value = wsID - setRequest.value.key.element_type = ELEMENT_TYPE_DEVICE - setRequest.value.key.label.value = label - setRequest.value.key.value.value = value - setRequest.value.key.device_id.value = deviceId - setRequest.value.remove.value = False - tagClient = self.ctx.getApiClient(TagAssignmentConfigServiceStub) - tagClient.Set(setRequest) - # assign the tag in cache - self._assignDevTagInCache(deviceId, label, value) - def _setRelevantTagAssigns(self, tags: Dict): ''' Sets the relevantTagAssigns of the context. @@ -236,10 +309,11 @@ def _getDeviceTags(self, deviceId: str): def _getAllDeviceTags(self): ''' - _getAllDeviceTags returns a map of all assigned device tags available in the workspace, + _getAllDeviceTags returns a map of all assigned device tags available + in the workspace. The returned map is of the form: map[deviceId]map[label]=[value1,value2,..] ''' - if self.relevantTagAssigns: + if self.relevantTagAssigns is not None: return self.relevantTagAssigns self._getAllDeviceTagsFromMainline() workspaceUpdates = self._getTagUpdatesFromWorkspace() @@ -250,14 +324,16 @@ def _getAllDeviceTags(self): self._assignDevTagInCache(deviceId, label, value) return self.relevantTagAssigns - def _assignDeviceTag(self, deviceId: str, label: str, value: str, replaceValue: bool = True): + def _assignDeviceTag(self, deviceId: str, label: str, value: str, + replaceValue: bool = True): ''' - _assignDeviceTag assigns a device tag if it isn't already assigned, - enforcing that only one value of the tag is assigned to the device, - unless the replaceValue argument is set to False + _assignDeviceTag assigns a device tag if it isn't already assigned. + If replaceValue is True ensures one value of tag is assigned to device. + If replaceValue is False multiple values of tag can be assigned to device. ''' # first make sure this device's tags have been loaded in cache self._getDeviceTags(deviceId) + # identify unassigns for replace cases if not label or not value or not deviceId: raise TagOperationException(label, value, 'assign', deviceId) if replaceValue: @@ -265,7 +341,15 @@ def _assignDeviceTag(self, deviceId: str, label: str, value: str, replaceValue: for cvalue in current_values: if cvalue != value: self._unassignDeviceTag(deviceId, label, cvalue) - self._assignDeviceTagSet(deviceId, label, value) + # check if the tag is already assigned to this device + if self._deviceTagAssigned(deviceId, label, value): + return + # create the tag + self._createTag(ELEMENT_TYPE_DEVICE, label, value) + # assign the tag + self._assignTagSet(ELEMENT_TYPE_DEVICE, (deviceId, '', label, value)) + # assign the tag in cache + self._assignDevTagInCache(deviceId, label, value) def _unassignDeviceTag(self, deviceId: str, label: str, value: str): ''' @@ -279,16 +363,8 @@ def _unassignDeviceTag(self, deviceId: str, label: str, value: str): if not self._deviceTagAssigned(deviceId, label, value): return # unassign the tag - setRequest = TagAssignmentConfigSetRequest() - wsID = self.ctx.getWorkspaceId() - setRequest.value.key.workspace_id.value = wsID - setRequest.value.key.element_type = ELEMENT_TYPE_DEVICE - setRequest.value.key.label.value = label - setRequest.value.key.value.value = value - setRequest.value.key.device_id.value = deviceId - setRequest.value.remove.value = True - tagClient = self.ctx.getApiClient(TagAssignmentConfigServiceStub) - tagClient.Set(setRequest) + self._assignTagSet(ELEMENT_TYPE_DEVICE, + (deviceId, '', label, value), remove=True) # unassign the tag in cache self._unassignDevTagInCache(deviceId, label, value) @@ -296,12 +372,91 @@ def _unassignDeviceTagLabel(self, deviceId: str, label: str): ''' _unassignDeviceTagLabel unassigns all device tags of a label ''' - current_values = list(self._getDeviceTags(deviceId).get(label, [])) if not label or not deviceId: raise TagOperationException(label, '', 'unassign', deviceId) + current_values = list(self._getDeviceTags(deviceId).get(label, [])) for cvalue in current_values: self._unassignDeviceTag(deviceId, label, cvalue) + def _assignDeviceTags(self, tagAssignReplaces: List[Tuple]): + ''' + _assignDeviceTags assigns multiple device tags if not already assigned. + The input tagAssignReplaces is a List of Tuple of the form: + (deviceId, label, value, replaceValue) + If replaceValue is True ensures one value of tag is assigned to device. + If replaceValue is False multiple values of tag can be assigned to device. + ''' + if not tagAssignReplaces: + return + tagUnAssigns: List[Tuple] = [] + tagAssigns: List[Tuple] = [] + tagAssignsDict: Dict = {} + # first make sure device tags have been loaded in cache + self._getAllDeviceTags() + # identify unassigns for replace cases + for (deviceId, label, value, replaceValue) in tagAssignReplaces: + if not label or not value or not deviceId: + raise TagOperationException(label, value, 'assign', deviceId) + if replaceValue: + current_values = list(self._getDeviceTags(deviceId).get(label, [])) + for cvalue in current_values: + if cvalue != value: + tagUnAssigns.append((deviceId, label, cvalue)) + if (cvalues := tagAssignsDict.get(deviceId, {}).get(label)): + for cvalue in cvalues: + tagAssigns.remove((deviceId, '', label, cvalue)) + tagAssignsDict[deviceId].pop(label) + tagAssignsDict.setdefault(deviceId, {}).setdefault(label, []).append(value) + tagAssigns.append((deviceId, '', label, value)) + self._unassignDeviceTags(tagUnAssigns) + # remove from the list if the tag assignmnet already exists + uniqueTags: List[Tuple] = [] + for (deviceId, _, label, value) in tagAssigns[:]: + if self._deviceTagAssigned(deviceId, label, value): + tagAssigns.remove((deviceId, '', label, value)) + elif (label, value) not in uniqueTags: + uniqueTags.append((label, value)) + if not tagAssigns: + return + # create the tags + self._createTags(ELEMENT_TYPE_DEVICE, uniqueTags) + # assign the tags + self._assignTagsSets(ELEMENT_TYPE_DEVICE, tagAssigns) + # assign the tags in cache + for (deviceId, _, label, value) in tagAssigns: + self._assignDevTagInCache(deviceId, label, value) + + def _unassignDeviceTags(self, tagUnAssignsIn: List[Tuple]): + ''' + _unassignDeviceTags unassigns multiple device tags if currently assigned. + The input tagUnAssigns is a List of Tuples of the form: + (deviceId, label, value) + If value is None then unassigns all values of that label + ''' + if not tagUnAssignsIn: + return + # first make sure device tags have been loaded in cache + self._getAllDeviceTags() + tagUnAssigns: List[Tuple] = [] + # remove from the list if the tag assignment doesn't exist + for (deviceId, label, value) in tagUnAssignsIn: + if not label or not deviceId: + raise TagOperationException(label, '', 'unassign', deviceId) + # check if the tag is assigned to this device + if not value: + current_values = list(self._getDeviceTags(deviceId).get(label, [])) + for cvalue in current_values: + tagUnAssigns.append((deviceId, '', label, cvalue)) + elif self._deviceTagAssigned(deviceId, label, value): + tagUnAssigns.append((deviceId, '', label, value)) + if not tagUnAssigns: + return + # unassign the tags + self._assignTagsSets(ELEMENT_TYPE_DEVICE, tagUnAssigns, remove=True) + # unassign the tags in cache + for (deviceId, _, label, value) in tagUnAssigns: + self._unassignDevTagInCache(deviceId, label, value) + # The following are methods for interface Tags def _interfaceTagExists(self, label: str, value: str): @@ -405,7 +560,7 @@ def _getAllInterfaceTags(self): in the workspace. The returned map is of the form: map[deviceId]map[interfaceId]map[label]=[value1,value2,..] ''' - if self.relevantIntfTagAssigns: + if self.relevantIntfTagAssigns is not None: return self.relevantIntfTagAssigns self._getAllInterfaceTagsFromMainline() workspaceUpdates = self._getTagUpdatesFromWorkspace(ELEMENT_TYPE_INTERFACE) @@ -416,36 +571,12 @@ def _getAllInterfaceTags(self): self._assignIntfTagInCache(deviceId, interfaceId, label, value) return self.relevantIntfTagAssigns - def _assignInterfaceTagSet(self, deviceId: str, interfaceId: str, label: str, value: str): - ''' - _assignInterfaceTagSet assigns a interface tag if it isn't already assigned - ''' - # check if the tag is already assigned to this device - if self._interfaceTagAssigned(deviceId, interfaceId, label, value): - return - # create the tag - self._createTag(ELEMENT_TYPE_INTERFACE, label, value) - # assign the tag - setRequest = TagAssignmentConfigSetRequest() - wsID = self.ctx.getWorkspaceId() - setRequest.value.key.workspace_id.value = wsID - setRequest.value.key.element_type = ELEMENT_TYPE_INTERFACE - setRequest.value.key.label.value = label - setRequest.value.key.value.value = value - setRequest.value.key.device_id.value = deviceId - setRequest.value.key.interface_id.value = interfaceId - setRequest.value.remove.value = False - tagClient = self.ctx.getApiClient(TagAssignmentConfigServiceStub) - tagClient.Set(setRequest) - # assign the tag in cache - self._assignIntfTagInCache(deviceId, interfaceId, label, value) - - def _assignInterfaceTag(self, deviceId: str, interfaceId: str, label: str, value: str, - replaceValue: bool = True): + def _assignInterfaceTag(self, deviceId: str, interfaceId: str, label: str, + value: str, replaceValue: bool = True): ''' - _assignInterfaceTag assigns a interface tag if it isn't already assigned, - enforcing that only one value of the tag is assigned to the interface, - unless the replaceValue argument is set to False + _assignInterfaceTag assigns aninterface tag if it isn't already assigned. + If replaceValue is True ensures one value of tag is assigned to interface. + If replaceValue is False multiple values of tag can be assigned to interface. ''' # first make sure this device's tags have been loaded in cache self._getInterfaceTags(deviceId, interfaceId) @@ -456,7 +587,16 @@ def _assignInterfaceTag(self, deviceId: str, interfaceId: str, label: str, value for cvalue in current_values: if cvalue != value: self._unassignInterfaceTag(deviceId, interfaceId, label, cvalue) - self._assignInterfaceTagSet(deviceId, interfaceId, label, value) + # check if the tag is already assigned to this device + if self._interfaceTagAssigned(deviceId, interfaceId, label, value): + return + # create the tag + self._createTag(ELEMENT_TYPE_INTERFACE, label, value) + # assign the tag + self._assignTagSet(ELEMENT_TYPE_INTERFACE, + (deviceId, interfaceId, label, value)) + # assign the tag in cache + self._assignIntfTagInCache(deviceId, interfaceId, label, value) def _unassignInterfaceTag(self, deviceId: str, interfaceId: str, label: str, value: str): ''' @@ -470,17 +610,8 @@ def _unassignInterfaceTag(self, deviceId: str, interfaceId: str, label: str, val if not self._interfaceTagAssigned(deviceId, interfaceId, label, value): return # unassign the tag - setRequest = TagAssignmentConfigSetRequest() - wsID = self.ctx.getWorkspaceId() - setRequest.value.key.workspace_id.value = wsID - setRequest.value.key.element_type = ELEMENT_TYPE_INTERFACE - setRequest.value.key.label.value = label - setRequest.value.key.value.value = value - setRequest.value.key.device_id.value = deviceId - setRequest.value.key.interface_id.value = interfaceId - setRequest.value.remove.value = True - tagClient = self.ctx.getApiClient(TagAssignmentConfigServiceStub) - tagClient.Set(setRequest) + self._assignTagSet(ELEMENT_TYPE_INTERFACE, + (deviceId, interfaceId, label, value), remove=True) # unassign the tag in cache self._unassignIntfTagInCache(deviceId, interfaceId, label, value) @@ -488,8 +619,93 @@ def _unassignInterfaceTagLabel(self, deviceId: str, interfaceId: str, label: str ''' _unassignInterfaceTagLabel unassigns all interface tags of a label ''' - current_values = list(self._getInterfaceTags(deviceId, interfaceId).get(label, [])) if not label or not deviceId or not interfaceId: raise TagOperationException(label, '', 'unassign', deviceId, interfaceId) + current_values = list(self._getInterfaceTags(deviceId, interfaceId).get(label, [])) for cvalue in current_values: self._unassignInterfaceTag(deviceId, interfaceId, label, cvalue) + + def _assignInterfaceTags(self, tagAssignReplaces: List[Tuple]): + ''' + _assignInterfaceTags assigns multiple interface tags if not already assigned. + The input tagAssignReplaces is a List of Tuple of the form: + (deviceId, interfaceId, label, value, replaceValue) + If replaceValue is True ensures one value of tag is assigned to interface. + If replaceValue is False multiple values of tag can be assigned to interface. + ''' + if not tagAssignReplaces: + return + tagUnAssigns: List[Tuple] = [] + tagAssigns: List[Tuple] = [] + tagAssignsDict: Dict = {} + # first make sure interface tags have been loaded in cache + self._getAllInterfaceTags() + # identify unassigns for replace cases + for (deviceId, interfaceId, label, value, replaceValue) in tagAssignReplaces: + if not label or not value or not deviceId or not interfaceId: + raise TagOperationException(label, value, 'assign', + deviceId, interfaceId) + if replaceValue: + current_values = list(self._getInterfaceTags(deviceId, + interfaceId).get(label, [])) + for cvalue in current_values: + if cvalue != value: + tagUnAssigns.append((deviceId, interfaceId, label, cvalue)) + if (cvalues := tagAssignsDict.get(deviceId, {}).get( + interfaceId, {}).get(label)): + for cvalue in cvalues: + tagAssigns.remove((deviceId, interfaceId, label, cvalue)) + tagAssignsDict[deviceId][interfaceId].pop(label) + tagAssignsDict.setdefault(deviceId, {}).setdefault( + interfaceId, {}).setdefault(label, []).append(value) + tagAssigns.append((deviceId, interfaceId, label, value)) + self._unassignInterfaceTags(tagUnAssigns) + # remove from the list if the tag assignmnet already exists + uniqueTags: List[Tuple] = [] + for (deviceId, interfaceId, label, value) in tagAssigns[:]: + if self._interfaceTagAssigned(deviceId, interfaceId, label, value): + tagAssigns.remove((deviceId, interfaceId, label, value)) + elif (label, value) not in uniqueTags: + uniqueTags.append((label, value)) + if not tagAssigns: + return + # create the tags + self._createTags(ELEMENT_TYPE_INTERFACE, uniqueTags) + # assign the tags + self._assignTagsSets(ELEMENT_TYPE_INTERFACE, tagAssigns) + # assign the tags in cache + for (deviceId, interfaceId, label, value) in tagAssigns: + self._assignIntfTagInCache(deviceId, interfaceId, label, value) + + def _unassignInterfaceTags(self, tagUnAssignsIn: List[Tuple]): + ''' + _unassignInterfaceTags unassigns multiple interface tags if currently assigned. + The input tagUnAssigns is a List of Tuples of the form: + (deviceId, interfaceId, label, value) + If value is None then unassigns all values of that label + ''' + if not tagUnAssignsIn: + return + # first make sure interface tags have been loaded in cache + self._getAllInterfaceTags() + tagUnAssigns: List[Tuple] = [] + # remove from the list if the tag assignment doesn't exist + for (deviceId, interfaceId, label, value) in tagUnAssignsIn: + if not label or not deviceId or not interfaceId: + raise TagOperationException(label, '', 'unassign', + deviceId, interfaceId) + # check if the tag is assigned to this interface + if not value: + current_values = list(self._getInterfaceTags(deviceId, + interfaceId).get(label, [])) + for cvalue in current_values: + tagUnAssigns.append((deviceId, interfaceId, label, cvalue)) + elif self._interfaceTagAssigned(deviceId, interfaceId, label, value): + tagUnAssigns.append((deviceId, interfaceId, label, value)) + if not tagUnAssigns: + return + # unassign the tags + self._assignTagsSets(ELEMENT_TYPE_INTERFACE, tagUnAssigns, remove=True) + # unassign the tags in cache + for (deviceId, interfaceId, label, value) in tagUnAssigns: + self._unassignIntfTagInCache(deviceId, interfaceId, label, value) diff --git a/test/cvlib/tags/test_deviceTags.py b/test/cvlib/tags/test_deviceTags.py index a9062ec6..04e3e95e 100644 --- a/test/cvlib/tags/test_deviceTags.py +++ b/test/cvlib/tags/test_deviceTags.py @@ -117,8 +117,7 @@ def getApiClient(self, stub): # err [ "get tag that is assigned correctly to device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -136,8 +135,7 @@ def getApiClient(self, stub): ], [ "get tag that is assigned to device with too many values", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC', 'DC2'), ('J1', 'DC-Pod', 'POD1'), @@ -156,8 +154,7 @@ def getApiClient(self, stub): ], [ "get required tag that isn't assigned to device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -175,8 +172,7 @@ def getApiClient(self, stub): ], [ "get unrequired tag that isn't assigned to device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -194,8 +190,7 @@ def getApiClient(self, stub): ], [ "try get required tag for device that has no tags", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -213,8 +208,7 @@ def getApiClient(self, stub): ], [ "try get unrequired tag for device that has no tags", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -315,8 +309,7 @@ def test_getSingleTag(name, cacheTags, getAllResp, topoDevices, deviceId, label, # expected Error [ "get all device tags for device that has tags", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -333,8 +326,7 @@ def test_getSingleTag(name, cacheTags, getAllResp, topoDevices, deviceId, label, ], [ "get specific label device tags for device that has tags", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -351,8 +343,7 @@ def test_getSingleTag(name, cacheTags, getAllResp, topoDevices, deviceId, label, ], [ "get all device tags for dev3 that has no tags", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -369,8 +360,7 @@ def test_getSingleTag(name, cacheTags, getAllResp, topoDevices, deviceId, label, ], [ "get specific label device tags for dev3 that has no tags", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -448,8 +438,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, label, # expected Error [ "assign new tag to device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -473,8 +462,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, label, ], [ "assign a new value of existing label to device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -498,8 +486,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, label, ], [ "replace value with new value of existing label for device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -522,8 +509,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, label, ], [ "replace value with already assigned value of another device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -546,8 +532,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, label, ], [ "assign already assigned tag to same device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -570,8 +555,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, label, ], [ "assign tag with invalid label to device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -594,8 +578,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, label, ], [ "assign tag with invalid value to device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -714,8 +697,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, # expected Error [ "unassign tag from device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -736,8 +718,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign one of two values for a label from device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC', 'DC2'), ('J1', 'DC-Pod', 'POD1'), @@ -760,8 +741,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign all values for a label from device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC', 'DC2'), ('J1', 'DC-Pod', 'POD1'), @@ -783,8 +763,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign a value that's not assigned for a label from device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -806,8 +785,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign a tag that's not assigned from device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -829,8 +807,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign tag with invalid label from device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), @@ -852,8 +829,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign tag with invalid label and valid value from device", - { - }, + None, convertListToStream([('J1', 'DC', 'DC1'), ('J1', 'DC-Pod', 'POD1'), ('J1', 'NodeId', '1'), diff --git a/test/cvlib/tags/test_interfaceTags.py b/test/cvlib/tags/test_interfaceTags.py index 3b9e6b36..81c42427 100644 --- a/test/cvlib/tags/test_interfaceTags.py +++ b/test/cvlib/tags/test_interfaceTags.py @@ -120,8 +120,7 @@ def getApiClient(self, stub): # err [ "get tag that is assigned correctly to interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet2', 'NodeId', '1'), @@ -140,8 +139,7 @@ def getApiClient(self, stub): ], [ "get tag that is assigned to interface with too many values", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC', 'DC2'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), @@ -161,8 +159,7 @@ def getApiClient(self, stub): ], [ "get required tag that isn't assigned to interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet2', 'NodeId', '1'), @@ -181,8 +178,7 @@ def getApiClient(self, stub): ], [ "get unrequired tag that isn't assigned to interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet2', 'NodeId', '1'), @@ -201,8 +197,7 @@ def getApiClient(self, stub): ], [ "try get required tag for interface that has no tags", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet2', 'NodeId', '1'), @@ -221,8 +216,7 @@ def getApiClient(self, stub): ], [ "try get unrequired tag for interface that has no tags", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet2', 'NodeId', '1'), @@ -331,8 +325,7 @@ def test_getSingleTag(name, cacheTags, getAllResp, topoDevices, deviceId, # expected Error [ "get all interface tags for interface that has tags", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -350,8 +343,7 @@ def test_getSingleTag(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "get specific label interface tags for interface that has tags", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -369,8 +361,7 @@ def test_getSingleTag(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "get all interface tags for dev3 ethernet1 that has no tags", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -388,8 +379,7 @@ def test_getSingleTag(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "get specific label interface tags for dev3 ethernet1 that has no tags", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -475,8 +465,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, # expected Error [ "assign new tag to interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -501,8 +490,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "assign a new value of existing label to interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -527,8 +515,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "replace value with new value of existing label for interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -552,8 +539,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "replace value with already assigned value of another interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -577,8 +563,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "assign already assigned tag to same interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -602,8 +587,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "assign tag with invalid label to interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -627,8 +611,7 @@ def test_getTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "assign tag with invalid value to interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -752,8 +735,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, # expected Error [ "unassign tag from interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -775,8 +757,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign one of two values for a label from interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC', 'DC2'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), @@ -800,8 +781,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign all values for a label from interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC', 'DC2'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), @@ -824,8 +804,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign a value that's not assigned for a label from interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -848,8 +827,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign a tag that's not assigned from interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -872,8 +850,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign tag with invalid label from interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), @@ -896,8 +873,7 @@ def test_assignTags(name, cacheTags, getAllResp, topoDevices, deviceId, ], [ "unassign tag with invalid label and valid value from interface", - { - }, + None, convertListToStream([('J1', 'Ethernet1', 'DC', 'DC1'), ('J1', 'Ethernet1', 'DC-Pod', 'POD1'), ('J1', 'Ethernet1', 'NodeId', '1'), diff --git a/test/cvlib/tags/test_tags.py b/test/cvlib/tags/test_tags.py index 19706db8..2fe571be 100644 --- a/test/cvlib/tags/test_tags.py +++ b/test/cvlib/tags/test_tags.py @@ -19,7 +19,10 @@ TagAssignmentServiceStub, TagAssignmentConfigServiceStub, TagAssignmentStreamResponse, - TagAssignmentConfigStreamResponse + TagAssignmentConfigStreamResponse, + TagAssignmentConfigSetSomeResponse, + TagConfigServiceStub, + TagConfigSetSomeResponse ) @@ -106,6 +109,12 @@ def SetGetAllConfigResponse(self, response): def Set(self, request): return + def SetSome(self, request): + if self.stub == TagConfigServiceStub: + return [TagConfigSetSomeResponse] + if self.stub == TagAssignmentConfigServiceStub: + return [TagAssignmentConfigSetSomeResponse] + class mockCtx(Context): def __init__(self): @@ -147,8 +156,7 @@ def getApiClient(self, stub): ], [ "no pre-existing cache", - { - }, + None, None, convertListToStream([('dev1', 'DC', 'DC1'), ('dev1', 'DC-Pod', 'POD1'), @@ -232,8 +240,7 @@ def test_getAllDeviceTags(name, cacheTags, validateFunc, getAllResp, ], [ "no preloaded tags", - { - }, + None, convertListToStream([('dev1', 'DC', 'DC1'), ('dev1', 'DC-Pod', 'POD1'), ('dev1', 'NodeId', '1'), @@ -248,8 +255,7 @@ def test_getAllDeviceTags(name, cacheTags, validateFunc, getAllResp, ], [ "no preloaded tags, device has no tags", - { - }, + None, convertListToStream([('dev1', 'DC', 'DC1'), ('dev1', 'DC-Pod', 'POD1'), ('dev1', 'NodeId', '1'), @@ -402,8 +408,8 @@ def test_getDeviceTags(name, cacheTags, getAllResp, deviceId, @pytest.mark.parametrize('name, getAllResp, deviceId, oper, operLabel, operValue, ' + 'replace, expNumGetAlls, expectedTags, expectedError', assignUnassignDeviceTagCases) -def test_changeDeviceTags(name, getAllResp, deviceId, oper, operLabel, operValue, - replace, expNumGetAlls, expectedTags, expectedError): +def test_changeDeviceTag(name, getAllResp, deviceId, oper, operLabel, operValue, + replace, expNumGetAlls, expectedTags, expectedError): error = None ctx = mockCtx() ctx.client.SetGetAllResponse(getAllResp) @@ -748,8 +754,7 @@ def test_mergeTags(name, mainlineStateResp, workspaceConfigResp, ], [ "get devices matching label without preloaded cache", - { - }, + None, convertListToStream([('dev1', 'DC', 'DC1'), ('dev1', 'DC-Pod', 'POD1'), ('dev1', 'NodeId', '1'), @@ -766,8 +771,7 @@ def test_mergeTags(name, mainlineStateResp, workspaceConfigResp, ], [ "try get devices not matching label without preloaded cache", - { - }, + None, convertListToStream([('dev1', 'DC', 'DC1'), ('dev1', 'DC-Pod', 'POD1'), ('dev1', 'NodeId', '1'), @@ -1018,8 +1022,7 @@ def test_getDevicesByTag(name, cacheTags, getAllResp, topoDevices, tag, ], [ "get interfaces matching label without preloaded cache", - { - }, + None, convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), ('dev1', 'Ethernet1', 'NodeId', '1'), @@ -1037,8 +1040,7 @@ def test_getDevicesByTag(name, cacheTags, getAllResp, topoDevices, tag, ], [ "try get interfaces not matching label without preloaded cache", - { - }, + None, convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), ('dev1', 'Ethernet1', 'NodeId', '1'), @@ -1084,3 +1086,880 @@ def test_getInterfacesByTag(name, cacheTags, getAllResp, topoDevices, tag, expIntfList = [(intf.name, intf._device.id) for intf in expectedInterfaces or []] assert intfList == expIntfList assert ctx.client.numGetAlls == expNumGetAlls + + +assignUnassignDeviceTagsCases = [ + # name + # tagv2 GetAll response + # assigns as List of (deviceId, label, value, replace) + # unassigns as List of (deviceId, label, value) + # operation ('assign', 'unassign') + # num GetAll calls + # expected tags + # expected error + [ + "assign additional Role tag value", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + [('dev1', 'Role', 'Core', False)], + None, + 'assign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], + 'Role':['Spine', 'Core']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf']}, + }, + None + ], + [ + "assign multiple additional Role tag values to same device", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + [('dev1', 'Role', 'Core', False), ('dev1', 'Role', 'RR', False)], + None, + 'assign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], + 'Role':['Spine', 'Core', 'RR']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf']}, + }, + None + ], + [ + "assign multiple additional Role tag values to multiple devices", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + [('dev1', 'Role', 'Core', False), ('dev1', 'Role', 'RR', False), + ('dev2', 'Role', 'Edge', False), ('dev2', 'Role', 'RR', False), + ], + None, + 'assign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], + 'Role':['Spine', 'Core', 'RR']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf', 'Edge', 'RR']}, + }, + None + ], + [ + "assign multiple additional new tags to multiple devices", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + [('dev1', 'AS', '65000', False), ('dev1', 'RouterId', '10.0.0.1', False), + ('dev2', 'AS', '65001', False), ('dev2', 'RouterId', '11.0.0.1', False), + ], + None, + 'assign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], + 'Role':['Spine'], 'AS': ['65000'], 'RouterId': ['10.0.0.1']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf'], 'AS': ['65001'], 'RouterId': ['11.0.0.1']}, + }, + None + ], + [ + "assign replacement Role tag value", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + [('dev1', 'Role', 'Core', True)], + None, + 'assign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], + 'Role':['Core']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf']}, + }, + None + ], + [ + "assign multiple replacement Role tag values", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + [('dev1', 'Role', 'Core', True), ('dev1', 'Role', 'RR', True)], + None, + 'assign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], + 'Role':['RR']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf']}, + }, + None + ], + [ + "assign multiple replacement tag values to multiple devices", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + [('dev1', 'Role', 'RR', True), ('dev1', 'DC-Pod', 'POD2', True), + ('dev2', 'NodeId', '3', True), ('dev2', 'DC-Pod', 'POD2', True), + ], + None, + 'assign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD2'], 'NodeId':['1'], + 'Role':['RR']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD2'], 'NodeId':['3'], + 'Role':['Leaf']}, + }, + None + ], + [ + "assign multiple additional and replacement tag values to multiple devices", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + [('dev1', 'Role', 'RR', False), ('dev1', 'DC-Pod', 'POD2', True), + ('dev2', 'NodeId', '3', True), ('dev2', 'DC-Pod', 'POD2', True), + ('dev2', 'Role', 'Edge', False), + ], + None, + 'assign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD2'], 'NodeId':['1'], + 'Role':['Spine', 'RR']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD2'], 'NodeId':['3'], + 'Role':['Leaf', 'Edge']}, + }, + None + ], + [ + "unassign second Role tag value", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev1', 'Role', 'Core'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'Role', 'Core')], + 'unassign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], + 'Role':['Spine']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf']}, + }, + None + ], + [ + "unassign last Role tag value", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'Role', 'Spine')], + 'unassign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf']}, + }, + None + ], + [ + "unassign tag label that's not assigned", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'AS', '65000')], + 'unassign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], + 'Role':['Spine']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf']}, + }, + None + ], + [ + "unassign tag value that's not assigned", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'Role', 'Core')], + 'unassign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], + 'Role':['Spine']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['2'], + 'Role':['Leaf']}, + }, + None + ], + [ + "unassign all role and nodeId tags from multiple devices", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev1', 'Role', 'Core'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ('dev2', 'Role', 'Edge'), + ]), + None, + [('dev1', 'Role', None), ('dev2', 'Role', None), + ('dev1', 'NodeId', None), ('dev2', 'NodeId', None), + ], + 'unassign', + 2, + { + 'dev1': {'DC': ['DC1'], 'DC-Pod': ['POD1']}, + 'dev2': {'DC': ['DC1'], 'DC-Pod': ['POD1']} + }, + None + ], + [ + "unassign all tags across multiple devices", + convertListToStream([('dev1', 'DC', 'DC1'), + ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), + ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), + ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), + ('dev2', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'DC', 'DC1'), ('dev1', 'DC-Pod', 'POD1'), + ('dev1', 'NodeId', '1'), ('dev1', 'Role', 'Spine'), + ('dev2', 'DC', 'DC1'), ('dev2', 'DC-Pod', 'POD1'), + ('dev2', 'NodeId', '2'), ('dev2', 'Role', 'Leaf'), + ], + 'unassign', + 2, + { + }, + None + ], +] + + +@pytest.mark.parametrize('name, getAllResp, assigns, unassigns, oper, ' + + 'expNumGetAlls, expectedTags, expectedError', + assignUnassignDeviceTagsCases) +def test_changeDeviceTags(name, getAllResp, assigns, unassigns, oper, + expNumGetAlls, expectedTags, expectedError): + error = None + ctx = mockCtx() + ctx.client.SetGetAllResponse(getAllResp) + try: + if oper == 'assign': + ctx.tags._assignDeviceTags(assigns) + elif oper == 'unassign': + ctx.tags._unassignDeviceTags(unassigns) + except Exception as e: + error = e + devTags = ctx.tags._getAllDeviceTags() + if error or expectedError: + assert str(error) == str(expectedError) + assert devTags == expectedTags + assert ctx.client.numGetAlls == expNumGetAlls + + +assignUnassignInterfaceTagCases = [ + # name + # tagv2 GetAll response + # device id + # interface id + # operation ('assign', 'unassign') + # operation tag label + # operation tag value + # replace flag + # num GetAll calls + # expected tags + # expected error + [ + "assign additional Role tag value", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + 'dev1', + 'Ethernet1', + 'assign', + 'Role', + 'Core', + False, + 2, + {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], 'Role':['Spine', 'Core']}, + None + ], + [ + "assign replacement Role tag value", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + 'dev1', + 'Ethernet1', + 'assign', + 'Role', + 'Core', + True, + 2, + {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], 'Role':['Core']}, + None + ], + [ + "unassign second Role tag value", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev1', 'Ethernet1', 'Role', 'Core'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + 'dev1', + 'Ethernet1', + 'unassign', + 'Role', + 'Core', + False, + 2, + {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], 'Role':['Spine']}, + None + ], + [ + "unassign last Role tag value", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + 'dev1', + 'Ethernet1', + 'unassign', + 'Role', + 'Spine', + False, + 2, + {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1']}, + None + ], + [ + "unassign tag value that's not assigned", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + 'dev1', + 'Ethernet1', + 'unassign', + 'Role', + 'Core', + False, + 2, + {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId':['1'], 'Role':['Spine']}, + None + ], +] + + +@pytest.mark.parametrize('name, getAllResp, deviceId, interfaceId, oper, operLabel, ' + + 'operValue, replace, expNumGetAlls, expectedTags, ' + + 'expectedError', + assignUnassignInterfaceTagCases) +def test_changeInterfaceTag(name, getAllResp, deviceId, interfaceId, oper, operLabel, + operValue, replace, expNumGetAlls, expectedTags, + expectedError): + error = None + ctx = mockCtx() + ctx.client.SetGetAllResponse(getAllResp) + try: + if oper == 'assign': + ctx.tags._assignInterfaceTag(deviceId, interfaceId, operLabel, operValue, + replaceValue=replace) + elif oper == 'unassign': + ctx.tags._unassignInterfaceTag(deviceId, interfaceId, operLabel, operValue) + except Exception as e: + error = e + intfTags = ctx.tags._getInterfaceTags(deviceId, interfaceId) + if error or expectedError: + assert str(error) == str(expectedError) + assert intfTags == expectedTags + assert ctx.client.numGetAlls == expNumGetAlls + + +assignUnassignInterfaceTagsCases = [ + # name + # tagv2 GetAll response + # assigns as List of (deviceId, interfaceId, label, value, replace) + # unassigns as List of (deviceId, interfaceId, label, value) + # operation ('assign', 'unassign') + # num GetAll calls + # expected tags + # expected error + [ + "assign additional Role tag value", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + [('dev1', 'Ethernet1', 'Role', 'Core', False)], + None, + 'assign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['1'], + 'Role': ['Spine', 'Core']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['2'], + 'Role': ['Leaf']}}, + }, + None + ], + [ + "assign multiple additional Role tag values to same interface", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + [('dev1', 'Ethernet1', 'Role', 'Core', False), + ('dev1', 'Ethernet1', 'Role', 'RR', False) + ], + None, + 'assign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['1'], + 'Role': ['Spine', 'Core', 'RR']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['2'], + 'Role': ['Leaf']}}, + }, + None + ], + [ + "assign multiple additional Role tag values to multiple interfaces", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + [('dev1', 'Ethernet1', 'Role', 'Core', False), + ('dev1', 'Ethernet1', 'Role', 'RR', False), + ('dev1', 'Ethernet2', 'Role', 'Core', False), + ('dev1', 'Ethernet2', 'Role', 'RR', False), + ('dev2', 'Ethernet1', 'Role', 'Edge', False), + ('dev2', 'Ethernet1', 'Role', 'RR', False) + ], + None, + 'assign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['1'], + 'Role': ['Spine', 'Core', 'RR']}, + 'Ethernet2': {'Role': ['Core', 'RR']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['2'], + 'Role': ['Leaf', 'Edge', 'RR']}}, + }, + None + ], + [ + "assign replacement Role tag value", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + [('dev1', 'Ethernet1', 'Role', 'Core', True)], + None, + 'assign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['1'], + 'Role': ['Core']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['2'], + 'Role': ['Leaf']}}, + }, + None + ], + [ + "assign multiple replacement Role tag values to multiple interfaces", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + [('dev1', 'Ethernet1', 'Role', 'Core', True), + ('dev1', 'Ethernet1', 'Role', 'RR', True), + ('dev1', 'Ethernet2', 'Role', 'Core', True), + ('dev1', 'Ethernet2', 'Role', 'RR', True) + ], + None, + 'assign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['1'], + 'Role': ['RR']}, + 'Ethernet2': {'Role': ['RR']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['2'], + 'Role': ['Leaf']}}, + }, + None + ], + [ + "assign multiple additional and replacement tag values to multiple interfaces", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ('dev2', 'Ethernet2', 'DC', 'DC1'), + ('dev2', 'Ethernet2', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet2', 'NodeId', '3'), + ('dev2', 'Ethernet2', 'Role', 'Leaf'), + ]), + [('dev1', 'Ethernet1', 'Role', 'RR', False), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD2', True), + ('dev1', 'Ethernet2', 'NodeId', '2', True), + ('dev1', 'Ethernet2', 'Role', 'Core', True), + ('dev1', 'Ethernet2', 'Role', 'RR', True), + ('dev2', 'Ethernet1', 'NodeId', '3', True), + ('dev2', 'Ethernet1', 'Role', 'Edge', False), + ('dev2', 'Ethernet2', 'NodeId', '4', True), + ('dev2', 'Ethernet2', 'Role', 'Edge', False), + ], + None, + 'assign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD2'], 'NodeId': ['1'], + 'Role':['Spine', 'RR']}, + 'Ethernet2': {'NodeId': ['2'], 'Role': ['RR']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['3'], + 'Role': ['Leaf', 'Edge']}, + 'Ethernet2': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['4'], + 'Role': ['Leaf', 'Edge']}}, + }, + None + ], + [ + "unassign second Role tag value", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev1', 'Ethernet1', 'Role', 'Core'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'Ethernet1', 'Role', 'Core')], + 'unassign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['1'], + 'Role': ['Spine']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['2'], + 'Role': ['Leaf']}}, + }, + None + ], + [ + "unassign last Role tag value", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'Ethernet1', 'Role', 'Spine')], + 'unassign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['1']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['2'], + 'Role': ['Leaf']}}, + }, + None + ], + [ + "unassign tag label that's not assigned", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'Ethernet1', 'AS', '65000')], + 'unassign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['1'], + 'Role': ['Spine']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['2'], + 'Role': ['Leaf']}}, + }, + None + ], + [ + "unassign tag value that's not assigned", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'Ethernet1', 'Role', 'Core')], + 'unassign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['1'], + 'Role': ['Spine']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1'], 'NodeId': ['2'], + 'Role': ['Leaf']}}, + }, + None + ], + [ + "unassign all role and nodeId tags from multiple interfaces", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev1', 'Ethernet1', 'Role', 'Core'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ('dev2', 'Ethernet1', 'Role', 'Edge'), + ('dev2', 'Ethernet2', 'DC', 'DC1'), + ('dev2', 'Ethernet2', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet2', 'NodeId', '3'), + ('dev2', 'Ethernet2', 'Role', 'Leaf'), + ('dev2', 'Ethernet2', 'Role', 'Edge'), + ]), + None, + [('dev1', 'Ethernet1', 'Role', None), + ('dev1', 'Ethernet1', 'NodeId', None), + ('dev2', 'Ethernet1', 'Role', None), + ('dev2', 'Ethernet1', 'NodeId', None), + ('dev2', 'Ethernet2', 'Role', None), + ('dev2', 'Ethernet2', 'NodeId', None), + ], + 'unassign', + 2, + { + 'dev1': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1']}}, + 'dev2': {'Ethernet1': {'DC': ['DC1'], 'DC-Pod': ['POD1']}, + 'Ethernet2': {'DC': ['DC1'], 'DC-Pod': ['POD1']}}, + }, + None + ], + [ + "unassign all tags across multiple interfaces", + convertListToStream([('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ('dev2', 'Ethernet2', 'DC', 'DC1'), + ('dev2', 'Ethernet2', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet2', 'NodeId', '3'), + ('dev2', 'Ethernet2', 'Role', 'Leaf'), + ]), + None, + [('dev1', 'Ethernet1', 'DC', 'DC1'), + ('dev1', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev1', 'Ethernet1', 'NodeId', '1'), + ('dev1', 'Ethernet1', 'Role', 'Spine'), + ('dev2', 'Ethernet1', 'DC', 'DC1'), + ('dev2', 'Ethernet1', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet1', 'NodeId', '2'), + ('dev2', 'Ethernet1', 'Role', 'Leaf'), + ('dev2', 'Ethernet2', 'DC', 'DC1'), + ('dev2', 'Ethernet2', 'DC-Pod', 'POD1'), + ('dev2', 'Ethernet2', 'NodeId', '3'), + ('dev2', 'Ethernet2', 'Role', 'Leaf'), + ], + 'unassign', + 2, + { + }, + None + ], +] + + +@pytest.mark.parametrize('name, getAllResp, assigns, unassigns, oper, ' + + 'expNumGetAlls, expectedTags, expectedError', + assignUnassignInterfaceTagsCases) +def test_changeInterfaceTags(name, getAllResp, assigns, unassigns, oper, + expNumGetAlls, expectedTags, expectedError): + error = None + ctx = mockCtx() + ctx.client.SetGetAllResponse(getAllResp) + try: + if oper == 'assign': + ctx.tags._assignInterfaceTags(assigns) + elif oper == 'unassign': + ctx.tags._unassignInterfaceTags(unassigns) + except Exception as e: + error = e + intfTags = ctx.tags._getAllInterfaceTags() + if error or expectedError: + assert str(error) == str(expectedError) + assert intfTags == expectedTags + assert ctx.client.numGetAlls == expNumGetAlls