# Copyright 2015 Cisco Systems, Inc.
#
# 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.
"""The naming module for the ACI Python SDK (cobra)."""
from builtins import next # pylint:disable=redefined-builtin
from builtins import str # pylint:disable=redefined-builtin
from builtins import object # pylint:disable=redefined-builtin
from cobra.mit.meta import ClassLoader
from collections import deque
[docs]class Rn(object):
"""The relative name (Rn) of the managed object (MO).
You can use Rn to convert between Rn of an MO its constituent naming
values. The string form of Rn is {prefix}{val1}{prefix2}{Val2} (...)
Note:
The naming value is enclosed in brackets ([]) if the meta object
specifies that properties be delimited.
Attributes:
namingVals (tupleiterator): An interator for the naming values - readonly
meta (cobra.mit.meta.ClassMeta): The class meta for this Rn - readonly
moClass (cobra.mit.mo.Mo): The class of the Mo for this Rn - readonly
"""
[docs] @classmethod
def fromString(cls, classMeta, rnStr):
"""Create a relative name instance from a string and classMeta.
Args:
classMeta (cobra.mit.meta.ClassMeta): class meta of the mo class
rnStr (str): string form of the Rn
Raises:
ValueError: If the Rn prefix is not valid or the Rn does not follow
the proper rnFormat
Returns:
cobra.mit.naming.Rn: The Rn object
"""
def findBalancedPropDelims(rn, start):
"""Find the matching closing naming value property delimitor.
Args:
rn (str): The Rn as a string.
start (int): The character location in the Rn to start parsing
at.
Raises:
ValueError: If a closing naming property delimiter is found
before a matching opening naming property delimiter.
Returns:
int: The character location in rn where the naming property ends.
"""
stk = deque()
first = True
end = start
# loop through the rnStr looking for [ and append them to the deque
# and ] and pop them off the deque, once things are balanced return
# that character location as the end of the Rn.
while end < len(rn):
if not first and len(stk) == 0:
return end
symbol = rn[end]
if symbol == "[":
if first and len(stk) == 0:
first = False
stk.append(symbol)
elif symbol == "]":
if first and len(stk) == 0:
raise ValueError("Invalid Rn: Found closing prop " +
"delimiter before opening prop " +
"delimiter.")
else:
stk.pop()
end += 1
return -1
# pylint:disable=too-many-branches
def parseNamingProps(meta, rn):
"""Parse the naming properties into a list.
Args:
meta (cobra.mit.meta.PropMeta): The property meta object.
rn (cobra.mit.naming.Rn): The relative name to parse for naming
properties.
Returns:
list: A list of naming properties.
"""
rnFormat = meta.rnFormat
if not len(meta.namingProps):
if rn == rnFormat:
return []
else:
raise ValueError('rn prefix mismatch')
nPropVals = []
rnLen = len(rn)
end = 0
start = 0
propMetaIter = iter(meta.namingProps)
needPropDelimiter = False
nPropMeta = None
for rnPrefix, hasProp in meta.rnPrefixes:
if start > end:
# Parse the naming prop value
if needPropDelimiter:
end = findBalancedPropDelims(rn, start)
else:
end = rnStr.find(rnPrefix, start)
if end == -1:
raise ValueError("rn prefix '%s' not found in '%s'" %
(rnPrefix, rn))
nPropVal = rn[start:end]
if needPropDelimiter:
nPropVal = nPropVal[1:-1]
if nPropMeta:
nPropVals.append(nPropVal)
start = end
# Find the rn prefix
if not rn.startswith(rnPrefix, start):
raise ValueError('rn "%s" must be %s' % (rn, rnFormat))
if hasProp:
nPropMeta = next(propMetaIter)
needPropDelimiter = nPropMeta.needDelimiter
start += len(rnPrefix)
end = rnLen
nPropVal = rn[start:end]
if needPropDelimiter:
nPropVal = nPropVal[1:-1]
if nPropMeta:
nPropVals.append(nPropVal)
return nPropVals
namingVals = parseNamingProps(classMeta, rnStr)
return Rn(classMeta, *namingVals)
[docs] def __init__(self, classMeta, *namingVals):
"""Initalize a Rn object.
Args:
classMeta (cobra.mit.meta.ClassMeta): class meta of the mo class
**namingVals: The naming values for the Rn
"""
self.__namingVals = namingVals
self.__meta = classMeta
self.__rnStr = None
@property
def namingVals(self):
"""Get the naming vals for this Rn as an iterator.
Returns:
iterator: The naming vals for this Rn.
"""
return iter(self.__namingVals)
@property
def meta(self):
"""Get the meta object for this Rn.
Returns:
cobra.mit.meta.ClassMeta: The meta object for this Rn.
"""
return self.__meta
@property
def moClass(self):
"""Get the Mo class from the meta for this Rn.
Returns:
cobra.mit.mo.Mo: The Mo class from the meta for this Rn.
"""
return self.__meta.getClass()
[docs] def __lt__(self, other):
"""Implement <."""
return str(self) < str(other)
[docs] def __le__(self, other):
"""Implement <=."""
return str(self) <= str(other)
[docs] def __eq__(self, other):
"""Implement ==."""
return str(self) == str(other)
[docs] def __ne__(self, other):
"""Implement !=."""
return str(self) != str(other)
[docs] def __gt__(self, other):
"""Implement >."""
return str(self) > str(other)
[docs] def __ge__(self, other):
"""Implement >=."""
return str(self) >= str(other)
def __str__(self):
"""Implement str()."""
if not self.__rnStr:
self.__rnStr = self.__makeRnStr()
return self.__rnStr
def __hash__(self):
"""Implement has()."""
return hash(str(self))
def __makeRnStr(self):
"""Build a Rn string based on the naming props from the meta if any.
Returns:
str: The string that represents this rn.
"""
if self.__meta.namingProps:
namingProps = {}
namingValsIter = iter(self.__namingVals)
for propMeta in self.__meta.namingProps:
namingProps[propMeta.name] = next(namingValsIter)
return self.__meta.rnFormat % namingProps
else:
return self.__meta.rnFormat
[docs]class Dn(object):
"""A Distinguised name class.
The distinguished name (Dn) uniquely identifies a managed object (MO).
A Dn is an ordered list of relative names, such as:
dn = rn1/rn2/rn3/....
In this example, the Dn provides a fully qualified path for **user-john**
from the top of the Mit to the Mo.
dn = "uni/userext/user-john"
Attributes:
rns (listiterator): Iterator for all the rns from topRoot to the target
Mo
meta (cobra.mit.meta.ClassMeta): class meta of the mo class for this Dn
moClass (cobra.mit.mo.Mo): Mo class for this Dn
contextRoot (cobra.mit.mo.Mo): The context root for this Dn
"""
[docs] def __init__(self, rns=None):
"""Initialize a Dn instance from list of Rn objects.
Args:
rns (list): list of Rns
"""
self.__dnStr = None
self.__rns = []
self.__class = ClassLoader.loadClass('cobra.model.top.Root')
self.__meta = self.__class.meta
if rns is None:
rns = []
for rn in rns:
self.appendRn(rn)
@property
def rns(self):
"""Get the Rn's that make up this Dn as an iterator.
Returns:
iterator: An iterator object reprsenting the Rn's for this Dn.
"""
return iter(self.__rns)
@property
def meta(self):
"""Get the meta object for this Dn.
Returns:
cobra.mit.meta.ClassMeta: The class meta for this Dn.
"""
return self.__meta
@property
def moClass(self):
"""Get the Mo class for this Dn.
Returns:
cobra.mit.mo.Mo: The Mo class for this Dn.
"""
return self.__class
@property
def contextRoot(self):
"""Get the Dn's context root.
Returns:
None: If the Dn has no context root.
cobra.mit.meta.ClassMeta: The class meta for this Dn's Rn.
"""
for rn in reversed(self.__rns):
if rn.meta.isContextRoot:
return rn.meta
return None
[docs] @classmethod
def fromString(cls, dnStr):
"""Create a distingushed name instance from a dn string.
Parses the dn string into its constituent Rn strings and creates the Rn
objects.
Args:
dnStr (str): string form of Dn
Raises:
ValueError: If an Rn in the Dn is found to not be consistent with the
ACI model
Returns (cobra.mit.naming.Dn): The Dn instance
"""
rnStrs = cls.__splitDnStr(dnStr)
newDn = Dn()
pMeta = newDn.meta
for rnStr in rnStrs:
rnMeta = cls.__findChild(pMeta, rnStr)
if rnMeta is None:
raise ValueError("Dn '%s' cannot contain '%s'" %
(str(newDn), rnStr))
rn = Rn.fromString(rnMeta, rnStr)
newDn.appendRn(rn)
pMeta = rnMeta
return newDn
[docs] @classmethod
def findCommonParent(cls, dns):
"""Find the common parent for the given set of dn objects.
Args:
dns (list): The Dn objects to find the common parent of
Returns:
cobra.mit.naming.Dn: Dn object of the common parent if any, else Dn
for topRoot
"""
def allRnsEqual(allDns, idx):
"""Check if all Rn's a specific level are equal.
Args:
allDns (list): The Dn objects that will be used in the comparison
idx (int): The index of the Rn in the Dn's to compare
Returns:
bool: True if the Rn's are equal up to the idx for every Dn in
allDns
"""
firstRn = None
for eachDn in allDns:
# pylint:disable=protected-access
currentRn = eachDn.__rns[idx]
if firstRn is None:
firstRn = currentRn
else:
if firstRn != currentRn:
return False
# All Rns at this level are equal
return True
if not dns:
return None
elif len(dns) == 1:
return dns[0]
index = 0
# pylint:disable=protected-access
maxLen = min([len(dn.__rns) for dn in dns])
while index < maxLen:
if allRnsEqual(dns, index):
index += 1
else:
break
if index == 0:
return Dn()
rns = dns[0].__rns # pylint:disable=protected-access
return Dn(rns[:index])
[docs] def rn(self, index=None):
"""Get a Rn at a specified index.
If index is None, then the Rn of the target Mo is returned
Args:
index (None or int): index of the Rn object, this must be between
0 and the length of the Dn (i.e. number of Rns) or None. The
default is None
Returns (cobra.mit.naming.Rn): Rn object at the specified index
"""
if index is None:
return self.__rns[-1]
return self.__rns[index]
[docs] def getAncestor(self, level):
"""Get the ancestor Dn based on the number of levels.
Args:
level (int): number of levels
Returns:
cobra.mit.naming.Dn: The Dn object of the ancestor as specified by
the level argument
"""
rns = self.__rns[:-level]
return Dn(rns)
[docs] def getParent(self):
"""Get the parent Dn of the current Dn.
Same as:
self.getAncetor(1)
Returns:
cobra.mit.naming.Dn: Dn object of the immediate parent
"""
return self.getAncestor(1)
[docs] def clone(self):
"""Get a new copy of this Dn.
Returns:
cobra.mit.naming.Dn: Copy of this Dn
"""
newDn = Dn()
for rn in self.__rns:
newDn.appendRn(rn)
return newDn
[docs] def appendRn(self, rn):
"""Append an Rn to this Dn.
Note:
This changes the target MO
Args:
rn (cobra.mit.naming.Rn): The Rn to append to this Dn
Raises:
ValueError: If the Dn can not contain the Rn
"""
rnClassName = rn.meta.className
if rnClassName == 'cobra.model.top.Root':
return
if rnClassName not in self.__meta.childClasses:
className = str(self.meta.className)
raise ValueError("'%s' cannot contain '%s'" % (className,
str(rnClassName)))
self.__rns.append(rn)
self.__meta = rn.meta
self.__class = rn.moClass
self.__dnStr = None
[docs] def isDescendantOf(self, ancestorDn):
"""Check if a Dn is a descendant of this Dn.
Args:
ancestorDn (cobra.mit.naming.Dn): Dn being compared for descendants
Returns:
boo: True if this Dn is a descendant of the other Dn else False
"""
ansDnStr = str(ancestorDn)
dnStr = str(self)
return (dnStr != ansDnStr and len(self) > len(ancestorDn) and
dnStr.startswith(ansDnStr))
[docs] def isAncestorOf(self, descendantDn):
""" Check if a Dn is an ancestor of this Dn.
Args:
descendantDn (cobra.mit.naming.Dn): Dn being compared for ancestary
Returns:
bool: True if this Dn is an ancestor of the other Dn else False
"""
return descendantDn.isDescendantOf(self)
def __len__(self):
"""Implement len()."""
return len(self.__rns)
def __str__(self):
"""Implement str()."""
if not self.__dnStr:
self.__dnStr = self.__makeDn()
return self.__dnStr
[docs] def __lt__(self, other):
"""Implement <."""
return str(self) < str(other)
[docs] def __le__(self, other):
"""Implement <=."""
return str(self) <= str(other)
[docs] def __eq__(self, other):
"""Implement ==."""
return str(self) == str(other)
[docs] def __ne__(self, other):
"""Implement !=."""
return str(self) != str(other)
[docs] def __gt__(self, other):
"""Implement >."""
return str(self) > str(other)
[docs] def __ge__(self, other):
"""Implement >=."""
return str(self) >= str(other)
def __hash__(self):
"""Implement hash()."""
return hash(str(self))
def __makeDn(self):
"""Make a Dn string from the rns in this Dn.
Returns:
str: The Dn string for this Dn object.
"""
rnStrs = []
for rn in self.__rns:
rnStrs.append(str(rn))
return '/'.join(rnStrs)
@classmethod
def __splitDnStr(cls, dnStr):
"""Split Dn strings into Rn strings.
Args:
dnStr (str): The Dn string to split
Raises:
ValueError: If the Dn has unbalanced delimiters
Returns:
list: A list of Rn strings.
"""
rnStrs = []
rnStr = ""
delimCount = 0
for dnChar in dnStr:
if delimCount == 0 and dnChar == '/':
# Found rn string, eat the char and capture the Rn
if rnStr:
rnStrs.append(rnStr)
rnStr = ""
elif dnChar == '[':
delimCount += 1
rnStr += dnChar
elif dnChar == ']':
delimCount -= 1
rnStr += dnChar
else:
rnStr += dnChar
if rnStr:
rnStrs.append(rnStr)
if delimCount != 0:
raise ValueError("Invalid dn '%s' with unbalanced delimiters" %
dnStr)
return rnStrs
@classmethod
def __findChild(cls, pMeta, rnStr):
"""Find the child given the parent meta and Rn string.
Args:
pMeta (cobra.mit.meta.ClassMeta): The parent classes meta object.
rnStr (str): A string for the Rn of the child that should be found.
Returns:
None: If no child is found.
cobra.mit.meta.ClassMeta: The child's meta object.
"""
# This method assumes that the childNamesAndRnPrefix in the meta
# is sorted with longest prefix first. This will allow us to match
# child prefixes that are sub strings. For example 'ac' and 'action'
# where the list will have 'action' first and then 'ac'. This way
# it is guaranteed that the longest prefix is matched first
for childClassName, childRnPrefix in pMeta.childNamesAndRnPrefix:
if rnStr.startswith(childRnPrefix):
childClass = pMeta.childClasses[childClassName]
return childClass.meta
return None