diff options
-rw-r--r-- | cobbler/IPy.py | 1267 | ||||
-rw-r--r-- | cobbler/api.py | 49 | ||||
-rw-r--r-- | cobbler/check.py | 47 | ||||
-rw-r--r-- | cobbler/collection.py | 25 | ||||
-rw-r--r-- | cobbler/distros.py | 1 | ||||
-rw-r--r-- | cobbler/item.py | 8 | ||||
-rw-r--r-- | cobbler/profile.py | 3 | ||||
-rw-r--r-- | cobbler/profiles.py | 1 | ||||
-rw-r--r-- | cobbler/serializable.py | 12 | ||||
-rw-r--r-- | cobbler/serializer.py | 19 | ||||
-rw-r--r-- | cobbler/serializer_tools.py | 218 | ||||
-rw-r--r-- | cobbler/settings.py | 34 | ||||
-rw-r--r-- | cobbler/sync.py | 66 | ||||
-rw-r--r-- | cobbler/systems.py | 1 | ||||
-rw-r--r-- | cobbler/util.py | 8 | ||||
-rw-r--r-- | cobbler/~api.py | 0 | ||||
-rw-r--r-- | tests/tests.py | 72 |
17 files changed, 198 insertions, 1633 deletions
diff --git a/cobbler/IPy.py b/cobbler/IPy.py deleted file mode 100644 index f0d2bcb..0000000 --- a/cobbler/IPy.py +++ /dev/null @@ -1,1267 +0,0 @@ -""" IPy - class and tools for handling of IPv4 and IPv6 Addresses and Networks. - -$HeadURL: http://svn.23.nu/svn/repos/IPy/trunk/IPy.py $ - -$Id: IPy.py 671 2004-08-22 21:02:29Z md $ - -The IP class allows a comfortable parsing and handling for most -notations in use for IPv4 and IPv6 Addresses and Networks. It was -greatly inspired bei RIPE's Perl module NET::IP's interface but -doesn't share the Implementation. It doesn't share non-CIDR netmasks, -so funky stuff lixe a netmask 0xffffff0f can't be done here. - - >>> ip = IP('127.0.0.0/30') - >>> for x in ip: - ... print x - ... - 127.0.0.0 - 127.0.0.1 - 127.0.0.2 - 127.0.0.3 - >>> ip2 = IP('0x7f000000/30') - >>> ip == ip2 - 1 - >>> ip.reverseNames() - ['0.0.0.127.in-addr.arpa.', '1.0.0.127.in-addr.arpa.', '2.0.0.127.in-addr.arpa.', '3.0.0.127.in-addr.arpa.'] - >>> ip.reverseName() - '0-3.0.0.127.in-addr.arpa.' - >>> ip.iptype() - 'PRIVATE' - -It can detect about a dozen different ways of expressing IP addresses -and networks, parse them and distinguish between IPv4 and IPv6 addresses. - - >>> IP('10.0.0.0/8').version() - 4 - >>> IP('::1').version() - 6 - >>> print IP(0x7f000001) - 127.0.0.1 - >>> print IP('0x7f000001') - 127.0.0.1 - >>> print IP('127.0.0.1') - 127.0.0.1 - >>> print IP('10') - 10.0.0.0 - >>> print IP('1080:0:0:0:8:800:200C:417A') - 1080:0000:0000:0000:0008:0800:200c:417a - >>> print IP('1080::8:800:200C:417A') - 1080:0000:0000:0000:0008:0800:200c:417a - >>> print IP('::1') - 0000:0000:0000:0000:0000:0000:0000:0001 - >>> print IP('::13.1.68.3') - 0000:0000:0000:0000:0000:0000:0d01:4403 - >>> print IP('127.0.0.0/8') - 127.0.0.0/8 - >>> print IP('127.0.0.0/255.0.0.0') - 127.0.0.0/8 - >>> print IP('127.0.0.0-127.255.255.255') - 127.0.0.0/8 - -Nearly all class methods which return a string have an optional -parameter 'wantprefixlen' which controlles if the prefixlen or netmask -is printed. Per default the prefilen is always shown if the net -contains more than one address. - -wantprefixlen == 0 / None don't return anything 1.2.3.0 -wantprefixlen == 1 /prefix 1.2.3.0/24 -wantprefixlen == 2 /netmask 1.2.3.0/255.255.255.0 -wantprefixlen == 3 -lastip 1.2.3.0-1.2.3.255 - -You can also change the defaults on an per-object basis by fiddeling with the class members - -NoPrefixForSingleIp -WantPrefixLen - - >>> IP('10.0.0.0/32').strNormal() - '10.0.0.0' - >>> IP('10.0.0.0/24').strNormal() - '10.0.0.0/24' - >>> IP('10.0.0.0/24').strNormal(0) - '10.0.0.0' - >>> IP('10.0.0.0/24').strNormal(1) - '10.0.0.0/24' - >>> IP('10.0.0.0/24').strNormal(2) - '10.0.0.0/255.255.255.0' - >>> IP('10.0.0.0/24').strNormal(3) - '10.0.0.0-10.0.0.255' - >>> ip = IP('10.0.0.0') - >>> print ip - 10.0.0.0 - >>> ip.NoPrefixForSingleIp = None - >>> print ip - 10.0.0.0/32 - >>> ip.WantPrefixLen = 3 - >>> print ip - 10.0.0.0-10.0.0.0 - - -Further Information might be available at http://c0re.jp/c0de/IPy/ - -Hacked 2001 by drt@un.bewaff.net - -TODO: - * better comparison (__cmp__ and friends) - * tests for __cmp__ - * always write hex values lowercase - * interpret 2001:1234:5678:1234/64 as 2001:1234:5678:1234::/64 - * move size in bits into class variables to get rid of some "if self._ipversion ..." - * support for base85 encoding - * support for output of IPv6 encoded IPv4 Addresses - * update address type tables - * first-last notation should be allowed for IPv6 - * add IPv6 docstring examples - * check better for negative parameters - * add addition / aggregation - * move reverse name stuff out of the classes and refactor it - * support for aggregation of more than two nets at once - * support for aggregation with "holes" - * support for finding common prefix - * '>>' and '<<' for prefix manipulation - * add our own exceptions instead ValueError all the time - * rename checkPrefix to checkPrefixOk - * add more documentation and doctests - * refactor -""" - -__rcsid__ = '$Id: IPy.py 671 2004-08-22 21:02:29Z md $' -__version__ = '0.42' - -import types - -# Definition of the Ranges for IPv4 IPs -# this should include www.iana.org/assignments/ipv4-address-space -# and www.iana.org/assignments/multicast-addresses -IPv4ranges = { - '0' : 'PUBLIC', # fall back - '00000000' : 'PRIVATE', # 0/8 - '00001010' : 'PRIVATE', # 10/8 - '01111111' : 'PRIVATE', # 127.0/8 - '1' : 'PUBLIC', # fall back - '101011000001' : 'PRIVATE', # 172.16/12 - '1100000010101000' : 'PRIVATE', # 192.168/16 - '11011111' : 'RESERVED', # 223/8 - '111' : 'RESERVED' # 224/3 - } - -# Definition of the Ranges for IPv6 IPs -# see also www.iana.org/assignments/ipv6-address-space, -# www.iana.org/assignments/ipv6-tla-assignments, -# www.iana.org/assignments/ipv6-multicast-addresses, -# www.iana.org/assignments/ipv6-anycast-addresses -IPv6ranges = { - '00000000' : 'RESERVED', # ::/8 - '00000001' : 'UNASSIGNED', # 100::/8 - '0000001' : 'NSAP', # 200::/7 - '0000010' : 'IPX', # 400::/7 - '0000011' : 'UNASSIGNED', # 600::/7 - '00001' : 'UNASSIGNED', # 800::/5 - '0001' : 'UNASSIGNED', # 1000::/4 - '0010000000000000' : 'RESERVED', # 2000::/16 Reserved - '0010000000000001' : 'ASSIGNABLE', # 2001::/16 Sub-TLA Assignments [RFC2450] - '00100000000000010000000': 'ASSIGNABLE IANA', # 2001:0000::/29 - 2001:01F8::/29 IANA - '00100000000000010000001': 'ASSIGNABLE APNIC', # 2001:0200::/29 - 2001:03F8::/29 APNIC - '00100000000000010000010': 'ASSIGNABLE ARIN', # 2001:0400::/29 - 2001:05F8::/29 ARIN - '00100000000000010000011': 'ASSIGNABLE RIPE', # 2001:0600::/29 - 2001:07F8::/29 RIPE NCC - '0010000000000010' : '6TO4', # 2002::/16 "6to4" [RFC3056] - '0011111111111110' : '6BONE', # 3FFE::/16 6bone Testing [RFC2471] - '0011111111111111' : 'RESERVED', # 3FFF::/16 Reserved - '010' : 'GLOBAL-UNICAST', # 4000::/3 - '011' : 'UNASSIGNED', # 6000::/3 - '100' : 'GEO-UNICAST', # 8000::/3 - '101' : 'UNASSIGNED', # A000::/3 - '110' : 'UNASSIGNED', # C000::/3 - '1110' : 'UNASSIGNED', # E000::/4 - '11110' : 'UNASSIGNED', # F000::/5 - '111110' : 'UNASSIGNED', # F800::/6 - '1111110' : 'UNASSIGNED', # FC00::/7 - '111111100' : 'UNASSIGNED', # FE00::/9 - '1111111010' : 'LINKLOCAL', # FE80::/10 - '1111111011' : 'SITELOCAL', # FEC0::/10 - '11111111' : 'MULTICAST', # FF00::/8 - '0' * 96 : 'IPV4COMP', # ::/96 - '0' * 80 + '1' * 16 : 'IPV4MAP', # ::FFFF:0:0/96 - '0' * 128 : 'UNSPECIFIED', # ::/128 - '0' * 127 + '1' : 'LOOPBACK' # ::1/128 - } - - -class IPint: - """Handling of IP addresses returning integers. - - Use class IP instead because some features are not implemented for - IPint.""" - - def __init__(self, data, ipversion = 0): - """Create an instance of an IP object. - - Data can be a network specification or a single IP. IP - Addresses can be specified in all forms understood by - parseAddress.() the size of a network can be specified as - - /prefixlen a.b.c.0/24 2001:658:22a:cafe::/64 - -lastIP a.b.c.0-a.b.c.255 2001:658:22a:cafe::-2001:658:22a:cafe:ffff:ffff:ffff:ffff - /decimal netmask a.b.c.d/255.255.255.0 not supported for IPv6 - - If no size specification is given a size of 1 address (/32 for - IPv4 and /128 for IPv6) is assumed. - - >>> print IP('127.0.0.0/8') - 127.0.0.0/8 - >>> print IP('127.0.0.0/255.0.0.0') - 127.0.0.0/8 - >>> print IP('127.0.0.0-127.255.255.255') - 127.0.0.0/8 - - See module documentation for more examples. - """ - - self.NoPrefixForSingleIp = 1 # Print no Prefixlen for /32 and /128 - self.WantPrefixLen = None # Do we want prefix printed by default? see _printPrefix() - - netbits = 0 - prefixlen = -1 - - # handling of non string values in constructor - if type(data) == types.IntType or type(data) == types.LongType: - self.ip = long(data) - if ipversion == 0: - if self.ip < 0x100000000L: - ipversion = 4 - else: - ipversion = 6 - if ipversion == 4: - prefixlen = 32 - elif ipversion == 6: - prefixlen = 128 - else: - raise ValueError, "only IPv4 and IPv6 supported" - self._ipversion = ipversion - self._prefixlen = prefixlen - # handle IP instance as an parameter - elif isinstance(data, IPint): - self._ipversion = data._ipversion - self._prefixlen = data._prefixlen - self.ip = data.ip - else: - # TODO: refactor me! - # splitting of a string into IP and prefixlen et. al. - x = data.split('-') - if len(x) == 2: - # a.b.c.0-a.b.c.255 specification ? - (ip, last) = x - (self.ip, parsedVersion) = parseAddress(ip) - if parsedVersion != 4: - raise ValueError, "first-last notation only allowed for IPv4" - (last, lastversion) = parseAddress(last) - if lastversion != 4: - raise ValueError, "last address should be IPv4, too" - if last < self.ip: - raise ValueError, "last address should be larger than first" - size = last - self.ip - netbits = _count1Bits(size) - elif len(x) == 1: - x = data.split('/') - # if no prefix is given use defaults - if len(x) == 1: - ip = x[0] - prefixlen = -1 - elif len(x) > 2: - raise ValueError, "only one '/' allowed in IP Address" - else: - (ip, prefixlen) = x - if prefixlen.find('.') != -1: - # check if the user might have used a netmask like - # a.b.c.d/255.255.255.0 - (netmask, vers) = parseAddress(prefixlen) - if vers != 4: - raise ValueError, "netmask must be IPv4" - prefixlen = _netmaskToPrefixlen(netmask) - elif len(x) > 2: - raise ValueError, "only one '-' allowed in IP Address" - else: - raise ValueError, "can't parse" - - (self.ip, parsedVersion) = parseAddress(ip) - if ipversion == 0: - ipversion = parsedVersion - if prefixlen == -1: - if ipversion == 4: - prefixlen = 32 - netbits - elif ipversion == 6: - prefixlen = 128 - netbits - else: - raise ValueError, "only IPv4 and IPv6 supported" - self._ipversion = ipversion - self._prefixlen = int(prefixlen) - - if not _checkNetaddrWorksWithPrefixlen(self.ip, self._prefixlen, self._ipversion): - raise ValueError, "%s goes not well with prefixlen %d" % (hex(self.ip), self._prefixlen) - - - def int(self): - """Return the first / base / network addess as an (long) integer. - - The same as IP[0]. - - >>> hex(IP('10.0.0.0/8').int()) - '0xA000000L' - """ - return self.ip - - def version(self): - """Return the IP version of this Object. - - >>> IP('10.0.0.0/8').version() - 4 - >>> IP('::1').version() - 6 - """ - return self._ipversion - - def prefixlen(self): - """Returns Network Prefixlen. - - >>> IP('10.0.0.0/8').prefixlen() - 8 - """ - return self._prefixlen - - def net(self): - """Return the base (first) address of a network as an (long) integer.""" - - return self.int() - - def broadcast(self): - """Return the broadcast (last) address of a network as an (long) integer. - - The same as IP[-1].""" - return self.int() + self.len() - 1 - - def _printPrefix(self, want): - """Prints Prefixlen/Netmask. - - Not really. In fact it is our universal Netmask/Prefixlen printer. - This is considered an internel function. - - want == 0 / None don't return anything 1.2.3.0 - want == 1 /prefix 1.2.3.0/24 - want == 2 /netmask 1.2.3.0/255.255.255.0 - want == 3 -lastip 1.2.3.0-1.2.3.255 - """ - - if (self._ipversion == 4 and self._prefixlen == 32) or \ - (self._ipversion == 6 and self._prefixlen == 128): - if self.NoPrefixForSingleIp: - want = 0 - if want == None: - want = self.WantPrefixLen - if want == None: - want = 1 - if want: - if want == 2: - # this should work wit IP and IPint - netmask = self.netmask() - if type(netmask) != types.IntType and type(netmask) != types.LongType: - netmask = netmask.int() - return "/%s" % (intToIp(netmask, self._ipversion)) - elif want == 3: - return "-%s" % (intToIp(self.ip + self.len() - 1, self._ipversion)) - else: - # default - return "/%d" % (self._prefixlen) - else: - return '' - - # We have different Favours to convert to: - # strFullsize 127.0.0.1 2001:0658:022a:cafe:0200:c0ff:fe8d:08fa - # strNormal 127.0.0.1 2001:658:22a:cafe:200:c0ff:fe8d:08fa - # strCompressed 127.0.0.1 2001:658:22a:cafe::1 - # strHex 0x7F000001L 0x20010658022ACAFE0200C0FFFE8D08FA - # strDec 2130706433 42540616829182469433547974687817795834 - - def strBin(self, wantprefixlen = None): - """Return a string representation as a binary value. - - >>> print IP('127.0.0.1').strBin() - 01111111000000000000000000000001 - """ - - - if self._ipversion == 4: - bits = 32 - elif self._ipversion == 6: - bits = 128 - else: - raise ValueError, "only IPv4 and IPv6 supported" - - if self.WantPrefixLen == None and wantprefixlen == None: - wantprefixlen = 0 - ret = _intToBin(self.ip) - return '0' * (bits - len(ret)) + ret + self._printPrefix(wantprefixlen) - - def strCompressed(self, wantprefixlen = None): - """Return a string representation in compressed format using '::' Notation. - - >>> print IP('127.0.0.1').strCompressed() - 127.0.0.1 - >>> print IP('2001:0658:022a:cafe:0200::1').strCompressed() - 2001:658:22a:cafe:200::1 - """ - - if self.WantPrefixLen == None and wantprefixlen == None: - wantprefixlen = 1 - - if self._ipversion == 4: - return self.strFullsize(wantprefixlen) - else: - # find the longest sequence of '0' - hextets = [int(x, 16) for x in self.strFullsize(0).split(':')] - # every element of followingzeros will contain the number of zeros - # following the corrospondending element of hextetes - followingzeros = [0] * 8 - for i in range(len(hextets)): - followingzeros[i] = _countFollowingZeros(hextets[i:]) - # compressionpos is the position where we can start removing zeros - compressionpos = followingzeros.index(max(followingzeros)) - if max(followingzeros) > 1: - # genererate string with the longest number of zeros cut out - # now we need hextets as strings - hextets = [x for x in self.strNormal(0).split(':')] - while compressionpos < len(hextets) and hextets[compressionpos] == '0': - del(hextets[compressionpos]) - hextets.insert(compressionpos, '') - if compressionpos + 1 >= len(hextets): - hextets.append('') - if compressionpos == 0: - hextets = [''] + hextets - return ':'.join(hextets) + self._printPrefix(wantprefixlen) - else: - return self.strNormal() + self._printPrefix(wantprefixlen) - - def strNormal(self, wantprefixlen = None): - """Return a string representation in the usual format. - - >>> print IP('127.0.0.1').strNormal() - 127.0.0.1 - >>> print IP('2001:0658:022a:cafe:0200::1').strNormal() - 2001:658:22a:cafe:200:0:0:1 - """ - - if self.WantPrefixLen == None and wantprefixlen == None: - wantprefixlen = 1 - - if self._ipversion == 4: - ret = self.strFullsize(0) - elif self._ipversion == 6: - ret = ':'.join([hex(x)[2:] for x in [int(x, 16) for x in self.strFullsize(0).split(':')]]) - else: - raise ValueError, "only IPv4 and IPv6 supported" - - - - return ret + self._printPrefix(wantprefixlen) - - def strFullsize(self, wantprefixlen = None): - """Return a string representation in the non mangled format. - - >>> print IP('127.0.0.1').strFullsize() - 127.0.0.1 - >>> print IP('2001:0658:022a:cafe:0200::1').strFullsize() - 2001:0658:022a:cafe:0200:0000:0000:0001 - """ - - if self.WantPrefixLen == None and wantprefixlen == None: - wantprefixlen = 1 - - return intToIp(self.ip, self._ipversion).lower() + self._printPrefix(wantprefixlen) - - def strHex(self, wantprefixlen = None): - """Return a string representation in hex format. - - >>> print IP('127.0.0.1').strHex() - 0x7F000001 - >>> print IP('2001:0658:022a:cafe:0200::1').strHex() - 0x20010658022ACAFE0200000000000001 - """ - - if self.WantPrefixLen == None and wantprefixlen == None: - wantprefixlen = 0 - - x = hex(self.ip) - if x[-1] == 'L': - x = x[:-1] - return x + self._printPrefix(wantprefixlen) - - def strDec(self, wantprefixlen = None): - """Return a string representation in decimal format. - - >>> print IP('127.0.0.1').strDec() - 2130706433 - >>> print IP('2001:0658:022a:cafe:0200::1').strDec() - 42540616829182469433547762482097946625 - """ - - if self.WantPrefixLen == None and wantprefixlen == None: - wantprefixlen = 0 - - x = str(self.ip) - if x[-1] == 'L': - x = x[:-1] - return x + self._printPrefix(wantprefixlen) - - def iptype(self): - """Return a description of the IP type ('PRIVATE', 'RESERVERD', etc). - - >>> print IP('127.0.0.1').iptype() - PRIVATE - >>> print IP('192.168.1.1').iptype() - PRIVATE - >>> print IP('195.185.1.2').iptype() - PUBLIC - >>> print IP('::1').iptype() - LOOPBACK - >>> print IP('2001:0658:022a:cafe:0200::1').iptype() - ASSIGNABLE RIPE - - The type information for IPv6 is out of sync with reality. - """ - - # this could be greatly improved - - if self._ipversion == 4: - iprange = IPv4ranges - elif self._ipversion == 6: - iprange = IPv6ranges - else: - raise ValueError, "only IPv4 and IPv6 supported" - - bits = self.strBin() - for i in range(len(bits), 0, -1): - if iprange.has_key(bits[:i]): - return iprange[bits[:i]] - return "unknown" - - - def netmask(self): - """Return netmask as an integer. - - >>> print hex(IP('195.185.0.0/16').netmask().int()) - 0xFFFF0000L - """ - - # TODO: unify with prefixlenToNetmask? - if self._ipversion == 4: - locallen = 32 - self._prefixlen - elif self._ipversion == 6: - locallen = 128 - self._prefixlen - else: - raise ValueError, "only IPv4 and IPv6 supported" - - return ((2L ** self._prefixlen) - 1) << locallen - - - def strNetmask(self): - """Return netmask as an string. Mostly useful for IPv6. - - >>> print IP('195.185.0.0/16').strNetmask() - 255.255.0.0 - >>> print IP('2001:0658:022a:cafe::0/64').strNetmask() - /64 - """ - - # TODO: unify with prefixlenToNetmask? - if self._ipversion == 4: - locallen = 32 - self._prefixlen - return intToIp(((2L ** self._prefixlen) - 1) << locallen, 4) - elif self._ipversion == 6: - locallen = 128 - self._prefixlen - return "/%d" % self._prefixlen - else: - raise ValueError, "only IPv4 and IPv6 supported" - - def len(self): - """Return the length of an subnet. - - >>> print IP('195.185.1.0/28').len() - 16 - >>> print IP('195.185.1.0/24').len() - 256 - """ - - if self._ipversion == 4: - locallen = 32 - self._prefixlen - elif self._ipversion == 6: - locallen = 128 - self._prefixlen - else: - raise ValueError, "only IPv4 and IPv6 supported" - - return 2L ** locallen - - - def __len__(self): - """Return the length of an subnet. - - Called to implement the built-in function len(). - It breaks with IPv6 Networks. Anybody knows how to fix this.""" - - # Python < 2.2 has this silly restriction which breaks IPv6 - # how about Python >= 2.2 ... ouch - it presists! - - return int(self.len()) - - - def __getitem__(self, key): - """Called to implement evaluation of self[key]. - - >>> ip=IP('127.0.0.0/30') - >>> for x in ip: - ... print hex(x.int()) - ... - 0x7F000000L - 0x7F000001L - 0x7F000002L - 0x7F000003L - >>> hex(ip[2].int()) - '0x7F000002L' - >>> hex(ip[-1].int()) - '0x7F000003L' - """ - - if type(key) != types.IntType and type(key) != types.LongType: - raise TypeError - if abs(key) >= self.len(): - raise IndexError - if key < 0: - key = self.len() - abs(key) - - return self.ip + long(key) - - - - def __contains__(self, item): - """Called to implement membership test operators. - - Should return true if item is in self, false otherwise. Item - can be other IP-objects, strings or ints. - - >>> print IP('195.185.1.1').strHex() - 0xC3B90101 - >>> 0xC3B90101L in IP('195.185.1.0/24') - 1 - >>> '127.0.0.1' in IP('127.0.0.0/24') - 1 - >>> IP('127.0.0.0/24') in IP('127.0.0.0/25') - 0 - """ - - item = IP(item) - if item.ip >= self.ip and item.ip < self.ip + self.len() - item.len() + 1: - return 1 - else: - return 0 - - - def overlaps(self, item): - """Check if two IP address ranges overlap. - - Returns 0 if the two ranged don't overlap, 1 if the given - range overlaps at the end and -1 if it does at the beginning. - - >>> IP('192.168.0.0/23').overlaps('192.168.1.0/24') - 1 - >>> IP('192.168.0.0/23').overlaps('192.168.1.255') - 1 - >>> IP('192.168.0.0/23').overlaps('192.168.2.0') - 0 - >>> IP('192.168.1.0/24').overlaps('192.168.0.0/23') - -1 - """ - - item = IP(item) - if item.ip >= self.ip and item.ip < self.ip + self.len(): - return 1 - elif self.ip >= item.ip and self.ip < item.ip + item.len(): - return -1 - else: - return 0 - - - def __str__(self): - """Dispatch to the prefered String Representation. - - Used to implement str(IP).""" - - return self.strFullsize() - - - def __repr__(self): - """Print a representation of the Object. - - Used to implement repr(IP). Returns a string which evaluates - to an identical Object (without the wnatprefixlen stuff - see - module docstring. - - >>> print repr(IP('10.0.0.0/24')) - IP('10.0.0.0/24') - """ - - return("IPint('%s')" % (self.strCompressed(1))) - - - def __cmp__(self, other): - """Called by comparison operations. - - Should return a negative integer if self < other, zero if self - == other, a positive integer if self > other. - - Networks with different prefixlen are considered non-equal. - Networks with the same prefixlen and differing addresses are - considered non equal but are compared by thair base address - integer value to aid sorting of IP objects. - - The Version of Objects is not put into consideration. - - >>> IP('10.0.0.0/24') > IP('10.0.0.0') - 1 - >>> IP('10.0.0.0/24') < IP('10.0.0.0') - 0 - >>> IP('10.0.0.0/24') < IP('12.0.0.0/24') - 1 - >>> IP('10.0.0.0/24') > IP('12.0.0.0/24') - 0 - - """ - - # Im not really sure if this is "the right thing to do" - if self._prefixlen < other.prefixlen(): - return (other.prefixlen() - self._prefixlen) - elif self._prefixlen > other.prefixlen(): - - # Fixed bySamuel Krempp <krempp@crans.ens-cachan.fr>: - - # The bug is quite obvious really (as 99% bugs are once - # spotted, isn't it ? ;-) Because of precedence of - # multiplication by -1 over the substraction, prefixlen - # differences were causing the __cmp__ function to always - # return positive numbers, thus the function was failing - # the basic assumptions for a __cmp__ function. - - # Namely we could have (a > b AND b > a), when the - # prefixlen of a and b are different. (eg let - # a=IP("1.0.0.0/24"); b=IP("2.0.0.0/16");) thus, anything - # could happen when launching a sort algorithm.. - # everything's in order with the trivial, attached patch. - - return (self._prefixlen - other.prefixlen()) * -1 - else: - if self.ip < other.ip: - return -1 - elif self.ip > other.ip: - return 1 - else: - return 0 - - - def __hash__(self): - """Called for the key object for dictionary operations, and by - the built-in function hash() Should return a 32-bit integer - usable as a hash value for dictionary operations. The only - required property is that objects which compare equal have the - same hash value - - >>> hex(IP('10.0.0.0/24').__hash__()) - '0xf5ffffe7' - """ - - thehash = int(-1) - ip = self.ip - while ip > 0: - thehash = (thehash & 0xffffffff) ^ (ip & 0x7fffffff) - ip = ip >> 32 - thehash = thehash ^ self._prefixlen - return int(thehash) - - -class IP(IPint): - """Class for handling IP Addresses and Networks.""" - - def net(self): - """Return the base (first) address of a network as an IP object. - - The same as IP[0]. - - >>> IP('10.0.0.0/8').net() - IP('10.0.0.0') - """ - return IP(IPint.net(self)) - - def broadcast(self): - """Return the broadcast (last) address of a network as an IP object. - - The same as IP[-1]. - - >>> IP('10.0.0.0/8').broadcast() - IP('10.255.255.255') - """ - return IP(IPint.broadcast(self)) - - def netmask(self): - """Return netmask as an IP object. - - >>> IP('10.0.0.0/8').netmask() - IP('255.0.0.0') - """ - return IP(IPint.netmask(self)) - - - def reverseNames(self): - """Return a list with values forming the reverse lookup. - - >>> IP('213.221.113.87/32').reverseNames() - ['87.113.221.213.in-addr.arpa.'] - >>> IP('213.221.112.224/30').reverseNames() - ['224.112.221.213.in-addr.arpa.', '225.112.221.213.in-addr.arpa.', '226.112.221.213.in-addr.arpa.', '227.112.221.213.in-addr.arpa.'] - >>> IP('127.0.0.0/24').reverseNames() - ['0.0.127.in-addr.arpa.'] - >>> IP('127.0.0.0/23').reverseNames() - ['0.0.127.in-addr.arpa.', '1.0.127.in-addr.arpa.'] - >>> IP('127.0.0.0/16').reverseNames() - ['0.127.in-addr.arpa.'] - >>> IP('127.0.0.0/15').reverseNames() - ['0.127.in-addr.arpa.', '1.127.in-addr.arpa.'] - >>> IP('128.0.0.0/8').reverseNames() - ['128.in-addr.arpa.'] - >>> IP('128.0.0.0/7').reverseNames() - ['128.in-addr.arpa.', '129.in-addr.arpa.'] - - """ - - if self._ipversion == 4: - ret =[] - # TODO: Refactor. Add support for IPint objects - if self.len() < 2**8: - for x in self: - ret.append(x.reverseName()) - elif self.len() < 2**16L: - for i in range(0, self.len(), 2**8): - ret.append(self[i].reverseName()[2:]) - elif self.len() < 2**24L: - for i in range(0, self.len(), 2**16): - ret.append(self[i].reverseName()[4:]) - else: - for i in range(0, self.len(), 2**24): - ret.append(self[i].reverseName()[6:]) - return ret - elif self._ipversion == 6: - s = hex(self.ip)[2:].lower() - if s[-1] == 'l': - s = s[:-1] - if self._prefixlen % 4 != 0: - raise NotImplementedError, "can't create IPv6 reverse names at sub nibble level" - s = list(s) - s.reverse() - s = '.'.join(s) - first_nibble_index = int(32 - (self._prefixlen / 4)) * 2 - return ["%s.ip6.int." % s[first_nibble_index:]] - else: - raise ValueError, "only IPv4 and IPv6 supported" - - - - def reverseName(self): - """Return the value for reverse lookup/PTR records as RfC 2317 look alike. - - RfC 2317 is an ugly hack which only works for sub-/24 e.g. not - for /23. Do not use it. Better set up a Zone for every - address. See reverseName for a way to arcive that. - - >>> print IP('195.185.1.1').reverseName() - 1.1.185.195.in-addr.arpa. - >>> print IP('195.185.1.0/28').reverseName() - 0-15.1.185.195.in-addr.arpa. - """ - - if self._ipversion == 4: - s = self.strFullsize(0) - s = s.split('.') - s.reverse() - first_byte_index = int(4 - (self._prefixlen / 8)) - if self._prefixlen % 8 != 0: - nibblepart = "%s-%s" % (s[3-(self._prefixlen / 8)], intToIp(self.ip + self.len() - 1, 4).split('.')[-1]) - if nibblepart[-1] == 'l': - nibblepart = nibblepart[:-1] - nibblepart += '.' - else: - nibblepart = "" - - s = '.'.join(s[first_byte_index:]) - return "%s%s.in-addr.arpa." % (nibblepart, s) - - elif self._ipversion == 6: - s = hex(self.ip)[2:].lower() - if s[-1] == 'l': - s = s[:-1] - if self._prefixlen % 4 != 0: - nibblepart = "%s-%s" % (s[self._prefixlen:], hex(self.ip + self.len() - 1)[2:].lower()) - if nibblepart[-1] == 'l': - nibblepart = nibblepart[:-1] - nibblepart += '.' - else: - nibblepart = "" - s = list(s) - s.reverse() - s = '.'.join(s) - first_nibble_index = int(32 - (self._prefixlen / 4)) * 2 - return "%s%s.ip6.int." % (nibblepart, s[first_nibble_index:]) - else: - raise ValueError, "only IPv4 and IPv6 supported" - - def __getitem__(self, key): - """Called to implement evaluation of self[key]. - - >>> ip=IP('127.0.0.0/30') - >>> for x in ip: - ... print str(x) - ... - 127.0.0.0 - 127.0.0.1 - 127.0.0.2 - 127.0.0.3 - >>> print str(ip[2]) - 127.0.0.2 - >>> print str(ip[-1]) - 127.0.0.3 - """ - return IP(IPint.__getitem__(self, key)) - - def __repr__(self): - """Print a representation of the Object. - - >>> IP('10.0.0.0/8') - IP('10.0.0.0/8') - """ - - return("IP('%s')" % (self.strCompressed(1))) - - def __add__(self, other): - """Emulate numeric objects through network aggregation""" - if self.prefixlen() != other.prefixlen(): - raise ValueError, "Only networks with the same prefixlen can be added." - if self.prefixlen < 1: - raise ValueError, "Networks with a prefixlen longer than /1 can't be added." - if self.version() != other.version(): - raise ValueError, "Only networks with the same IP version can be added." - if self > other: - # fixed by Skinny Puppy <skin_pup-IPy@happypoo.com> - return other.__add__(self) - else: - ret = IP(self.int()) - ret._prefixlen = self.prefixlen() - 1 - return ret - -def parseAddress(ipstr): - """Parse a string and return the corrospondending IPaddress and the a guess of the IP version. - - Following Forms ar recorgnized: - 0x0123456789abcdef # IPv4 if <= 0xffffffff else IPv6 - 123.123.123.123 # IPv4 - 123.123 # 0-padded IPv4 - 1080:0000:0000:0000:0008:0800:200C:417A - 1080:0:0:0:8:800:200C:417A - 1080:0::8:800:200C:417A - ::1 - :: - 0:0:0:0:0:FFFF:129.144.52.38 - ::13.1.68.3 - ::FFFF:129.144.52.38 - """ - - # TODO: refactor me! - if ipstr.startswith('0x'): - ret = long(ipstr[2:], 16) - if ret > 0xffffffffffffffffffffffffffffffffL: - raise ValueError, "%r: IP Address can't be bigger than 2^128" % (ipstr) - if ret < 0x100000000L: - return (ret, 4) - else: - return (ret, 6) - - if ipstr.find(':') != -1: - # assume IPv6 - if ipstr.find(':::') != -1: - raise ValueError, "%r: IPv6 Address can't contain ':::'" % (ipstr) - hextets = ipstr.split(':') - if ipstr.find('.') != -1: - # this might be a mixed address like '0:0:0:0:0:0:13.1.68.3' - (v4, foo) = parseAddress(hextets[-1]) - assert foo == 4 - del(hextets[-1]) - hextets.append(hex(v4 >> 16)[2:-1]) - hextets.append(hex(v4 & 0xffff)[2:-1]) - if len(hextets) > 8: - raise ValueError, "%r: IPv6 Address with more than 8 hexletts" % (ipstr) - if len(hextets) < 8: - if '' not in hextets: - raise ValueError, "%r IPv6 Address with less than 8 hexletts and without '::'" % (ipstr) - # catch :: at the beginning or end - if hextets.index('') < len(hextets) - 1 and hextets[hextets.index('')+1] == '': - hextets.remove('') - # catch '::' - if hextets.index('') < len(hextets) - 1 and hextets[hextets.index('')+1] == '': - hextets.remove('') - - for foo in range(9-len(hextets)): - hextets.insert(hextets.index(''), '0') - hextets.remove('') - if '' in hextets: - raise ValueError, "%r IPv6 Address may contain '::' only once" % (ipstr) - if '' in hextets: - raise ValueError, "%r IPv6 Address may contain '::' only if it has less than 8 hextets" % (ipstr) - num = '' - for x in hextets: - if len(x) < 4: - x = ((4 - len(x)) * '0') + x - if int(x, 16) < 0 or int(x, 16) > 0xffff: - raise ValueError, "%r: single hextet must be 0 <= hextet <= 0xffff which isn't true for %s" % (ipstr, x) - num += x - return (long(num, 16), 6) - - elif len(ipstr) == 32: - # assume IPv6 in pure hexadecimal notation - return (long(ipstr, 16), 6) - - elif ipstr.find('.') != -1 or (len(ipstr) < 4 and int(ipstr) < 256): - # assume IPv4 ('127' gets interpreted as '127.0.0.0') - bytes = ipstr.split('.') - if len(bytes) > 4: - raise ValueError, "IPv4 Address with more than 4 bytes" - bytes += ['0'] * (4 - len(bytes)) - bytes = [long(x) for x in bytes] - for x in bytes: - if x > 255 or x < 0: - raise ValueError, "%r: single byte must be 0 <= byte < 256" % (ipstr) - return ((bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3], 4) - - else: - # we try to interprete it as a decimal digit - - # this ony works for numbers > 255 ... others - # will be interpreted as IPv4 first byte - ret = long(ipstr) - if ret > 0xffffffffffffffffffffffffffffffffL: - raise ValueError, "IP Address cant be bigger than 2^128" - if ret <= 0xffffffffL: - return (ret, 4) - else: - return (ret, 6) - - -def intToIp(ip, version): - """Transform an integer string into an IP address.""" - - # just to be sure and hoping for Python 2.22 - ip = long(ip) - - if ip < 0: - raise ValueError, "IPs can't be negative: %d" % (ip) - - ret = '' - if version == 4: - if ip > 0xffffffffL: - raise ValueError, "IPv4 Addresses can't be larger than 0xffffffff: %s" % (hex(ip)) - for l in range(4): - ret = str(ip & 0xffL) + '.' + ret - ip = ip >> 8; - ret = ret[:-1] - elif version == 6: - if ip > 0xffffffffffffffffffffffffffffffffL: - raise ValueError, "IPv6 Addresses can't be larger than 0xffffffffffffffffffffffffffffffff: %s" % (hex(ip)) - l = '0' * 32 + hex(ip)[2:-1] - for x in range(1,33): - ret = l[-x] + ret - if x % 4 == 0: - ret = ':' + ret - ret = ret[1:] - else: - raise ValueError, "only IPv4 and IPv6 supported" - - return ret; - -def _ipVersionToLen(version): - """Return number of bits in address for a certain IP version. - - >>> _ipVersionToLen(4) - 32 - >>> _ipVersionToLen(6) - 128 - >>> _ipVersionToLen(5) - Traceback (most recent call last): - File "<stdin>", line 1, in ? - File "IPy.py", line 1076, in _ipVersionToLen - raise ValueError, "only IPv4 and IPv6 supported" - ValueError: only IPv4 and IPv6 supported - """ - - if version == 4: - return 32 - elif version == 6: - return 128 - else: - raise ValueError, "only IPv4 and IPv6 supported" - - -def _countFollowingZeros(l): - """Return Nr. of elements containing 0 at the beginning th the list.""" - if len(l) == 0: - return 0 - elif l[0] != 0: - return 0 - else: - return 1 + _countFollowingZeros(l[1:]) - - -_BitTable = {'0': '0000', '1': '0001', '2': '0010', '3': '0011', - '4': '0100', '5': '0101', '6': '0110', '7': '0111', - '8': '1000', '9': '1001', 'a': '1010', 'b': '1011', - 'c': '1100', 'd': '1101', 'e': '1110', 'f': '1111'} - -def _intToBin(val): - """Return the binary representation of an integer as string.""" - - if val < 0: - raise ValueError, "Only positive Values allowed" - s = hex(val).lower() - ret = '' - if s[-1] == 'l': - s = s[:-1] - for x in s[2:]: - if __debug__: - if not _BitTable.has_key(x): - raise AssertionError, "hex() returned strange result" - ret += _BitTable[x] - # remove leading zeros - while ret[0] == '0' and len(ret) > 1: - ret = ret[1:] - return ret - -def _count1Bits(num): - """Find the highest bit set to 1 in an integer.""" - ret = 0 - while num > 0: - num = num >> 1 - ret += 1 - return ret - -def _count0Bits(num): - """Find the highest bit set to 0 in an integer.""" - - # this could be so easy if _count1Bits(~long(num)) would work as excepted - num = long(num) - if num < 0: - raise ValueError, "Only positive Numbers please: %s" % (num) - ret = 0 - while num > 0: - if num & 1 == 1: - break - num = num >> 1 - ret += 1 - return ret - - -def _checkPrefix(ip, prefixlen, version): - """Check the validity of a prefix - - Checks if the variant part of a prefix only has 0s, and the length is - correct. - - >>> _checkPrefix(0x7f000000L, 24, 4) - 1 - >>> _checkPrefix(0x7f000001L, 24, 4) - 0 - >>> repr(_checkPrefix(0x7f000001L, -1, 4)) - 'None' - >>> repr(_checkPrefix(0x7f000001L, 33, 4)) - 'None' - """ - - # TODO: unify this v4/v6/invalid code in a function - bits = _ipVersionToLen(version) - - if prefixlen < 0 or prefixlen > bits: - return None - - if ip == 0: - zbits = bits + 1 - else: - zbits = _count0Bits(ip) - if zbits < bits - prefixlen: - return 0 - else: - return 1 - - -def _checkNetmask(netmask, masklen): - """Checks if a netmask is expressable as e prefixlen.""" - - num = long(netmask) - bits = masklen - - # remove zero bits at the end - while (num & 1) == 0: - num = num >> 1 - bits -= 1 - if bits == 0: - break - # now check if the rest consists only of ones - while bits > 0: - if (num & 1) == 0: - raise ValueError, "Netmask %s can't be expressed as an prefix." % (hex(netmask)) - num = num >> 1 - bits -= 1 - - -def _checkNetaddrWorksWithPrefixlen(net, prefixlen, version): - """Check if a base addess of e network is compatible with a prefixlen""" - if net & _prefixlenToNetmask(prefixlen, version) == net: - return 1 - else: - return 0 - - -def _netmaskToPrefixlen(netmask): - """Convert an Integer reprsenting a Netmask to an prefixlen. - - E.g. 0xffffff00 (255.255.255.0) returns 24 - """ - - netlen = _count0Bits(netmask) - masklen = _count1Bits(netmask) - _checkNetmask(netmask, masklen) - return masklen - netlen - - -def _prefixlenToNetmask(prefixlen, version): - """Return a mask of n bits as a long integer. - - From 'IP address conversion functions with the builtin socket module' by Alex Martelli - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66517 - """ - if prefixlen == 0: - return 0 - elif prefixlen < 0: - raise ValueError, "Prefixlen must be > 0" - return ((2L<<prefixlen-1)-1) << (_ipVersionToLen(version) - prefixlen) - - -def _test(): - import doctest, IPy - return doctest.testmod(IPy) - -if __name__ == "__main__": - _test() - - t = [0xf0, 0xf00, 0xff00, 0xffff00, 0xffffff00L] - o = [] - for x in t: - pass - x = 0L diff --git a/cobbler/api.py b/cobbler/api.py index 0c0141f..d93bf54 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -17,57 +17,44 @@ from msg import * class BootAPI: + _config = config.Config() def __init__(self): """ Constructor... - """ - # if the file already exists, load real data now - try: - if config.files_exist(): - config.deserialize() - except Exception, e: - # parse errors, attempt to recover - print runtime.last_error() - if runtime.last_error() == m("parse_error"): - # it's bad, raise it so we can croak - raise Exception, "parse_error" - try: - config.serialize() - except: - # it's bad, raise it so we can croak - traceback.print_exc() - raise Exception, "parse_error2" - if not config.files_exist(): - config.serialize() + """ + + # FIXME: deserializer/serializer error + # handling probably not up to par yet + _config.deserialize() def clear(self): """ Forget about current list of profiles, distros, and systems """ - config.clear() + _config.clear() - def get_systems(self): + def systems(self): """ Return the current list of systems """ - return config.get_systems() + return _config.systems() - def get_profiles(self): + def profiles(self): """ Return the current list of profiles """ - return config.get_profiles() + return _config.profiles() - def get_distros(self): + def distros(self): """ Return the current list of distributions """ - return config.get_distros() + return _config.distros() def new_system(self): @@ -100,7 +87,7 @@ class BootAPI: for human admins, who may, for instance, forget to properly set up their TFTP servers for PXE, etc. """ - return check.bootcheck().run() + return check.bootcheck(_config).run() def sync(self,dry_run=True): @@ -110,19 +97,19 @@ class BootAPI: /tftpboot. Any operations done in the API that have not been saved with serialize() will NOT be synchronized with this command. """ - config.deserialize(); - return sync.bootsync(self).sync(dry_run) + # config.deserialize(); # neccessary? + return sync.bootsync(_config).sync(dry_run) def serialize(self): """ Save the config file(s) to disk. """ - config.serialize() + _config.serialize() def deserialize(self): """ Load the current configuration from config file(s) """ - config.deserialize() + _config.deserialize() diff --git a/cobbler/check.py b/cobbler/check.py index ce0fdaf..fd3291f 100644 --- a/cobbler/check.py +++ b/cobbler/check.py @@ -5,16 +5,15 @@ import os import sys import re -import weakref +import config from msg import * class BootCheck: - def __init__(self, api): - self.api = weakref.proxy(api) - self.config = weakref.proxy(self.api.config) - + def __init__(self,config): + self.config = config + self.settings = config.settings() def run(self): """ @@ -34,11 +33,11 @@ class BootCheck: return status def check_name(self,status): - if self.config.server == "localhost": + if self.settings.server == "localhost": status.append(m("bad_server")) def check_httpd(self,status): - if not os.path.exists(self.config.httpd_bin): + if not os.path.exists(self.settings.httpd_bin): status.append(m("no_httpd")) @@ -46,29 +45,29 @@ class BootCheck: """ Check if dhcpd is installed """ - if not os.path.exists(self.config.dhcpd_bin): + if not os.path.exists(self.settings.dhcpd_bin): status.append(m("no_dhcpd")) def check_pxelinux_bin(self,status): """ Check if pxelinux (part of syslinux) is installed """ - if not os.path.exists(self.config.pxelinux): + if not os.path.exists(self.settings.pxelinux): status.append(m("no_pxelinux")) def check_tftpd_bin(self,status): """ Check if tftpd is installed """ - if not os.path.exists(self.config.tftpd_bin): + if not os.path.exists(self.settings.tftpd_bin): status.append(m("no_tftpd")) def check_tftpd_dir(self,status): """ Check if cobbler.conf's tftpboot directory exists """ - if not os.path.exists(self.config.tftpboot): - status.append(m("no_dir") % self.config.tftpboot) + if not os.path.exists(self.settings.tftpboot): + status.append(m("no_dir") % self.settings.tftpboot) def check_tftpd_conf(self,status): @@ -76,22 +75,22 @@ class BootCheck: Check that configured tftpd boot directory matches with actual Check that tftpd is enabled to autostart """ - if os.path.exists(self.config.tftpd_conf): - f = open(self.config.tftpd_conf) + if os.path.exists(self.settings.tftpd_conf): + f = open(self.settings.tftpd_conf) re_1 = re.compile(r'default:.*off') re_2 = re.compile(r'disable.*=.*yes') found_bootdir = False for line in f.readlines(): if re_1.search(line): - status.append(m("chg_attrib") % ('default','on',self.config.tftpd_conf)) + status.append(m("chg_attrib") % ('default','on',self.settings.tftpd_conf)) if re_2.search(line): - status.append(m("chg_attrib") % ('disable','no',self.config.tftpd_conf)) - if line.find("-s %s" % self.config.tftpboot) != -1: + status.append(m("chg_attrib") % ('disable','no',self.settings.tftpd_conf)) + if line.find("-s %s" % self.settings.tftpboot) != -1: found_bootdir = True if not found_bootdir: - status.append(m("chg_attrib") % ('server_args',"-s %s" % self.config.tftpboot, self.config.tftpd_conf)) + status.append(m("chg_attrib") % ('server_args',"-s %s" % self.settings.tftpboot, self.settings.tftpd_conf)) else: - status.append(m("no_exist") % self.config.tftpd_conf) + status.append(m("no_exist") % self.settings.tftpd_conf) def check_dhcpd_conf(self,status): @@ -99,20 +98,20 @@ class BootCheck: Check that dhcpd *appears* to be configured for pxe booting. We can't assure file correctness """ - if os.path.exists(self.config.dhcpd_conf): + if os.path.exists(self.settings.dhcpd_conf): match_next = False match_file = False - f = open(self.config.dhcpd_conf) + f = open(self.settings.dhcpd_conf) for line in f.readlines(): if line.find("next-server") != -1: match_next = True if line.find("filename") != -1: match_file = True if not match_next: - status.append(m("no_line") % (self.config.dhcpd_conf, 'next-server ip-address')) + status.append(m("no_line") % (self.settings.dhcpd_conf, 'next-server ip-address')) if not match_file: - status.append(m("no_line") % (self.config.dhcpd_conf, 'filename "%s/pxelinux.0";' % self.config.tftpboot)) + status.append(m("no_line") % (self.settings.dhcpd_conf, 'filename "%s/pxelinux.0";' % self.settings.tftpboot)) else: - status.append(m("no_exist") % self.config.dhcpd_conf) + status.append(m("no_exist") % self.settings.dhcpd_conf) diff --git a/cobbler/collection.py b/cobbler/collection.py index 196bf7d..5a008f7 100644 --- a/cobbler/collection.py +++ b/cobbler/collection.py @@ -2,20 +2,15 @@ """ Base class for any serializable lists of things... """ -class Collection: +class Collection(Serializable): _item_factory = None + _filename = None def __init__(self): """ - Constructor. Requires an API reference. seed_data - is a hash of data to feed into the collection, that would - come from the config file in /var. + Constructor. """ self.listing = {} - # no longer done at construct time, use from_datastruct - #if seed_data is not None: - # for x in seed_data: - # self.add(self._item_factory(self.api, x)) def find(self,name): """ @@ -29,11 +24,9 @@ class Collection: def to_datastruct(self): """ - Return datastructure representation of this collection suitable - for feeding to a serializer (such as YAML) + Serialize the collection """ - return [x.to_datastruct() for x in self.listing.values()] - + datastruct = [x.to_datastruct() for x in self.listing.values()] def from_datastruct(self,datastruct): for x in datastruct: @@ -66,14 +59,6 @@ class Collection: else: return m("empty_list") - #def contents(self): - # """ - # Access the raw contents of the collection. Classes shouldn't - # be doing this (preferably) and should use the __iter__ interface. - # Deprecrated. - # """ - # return self.listing.values() - def __iter__(self): """ Iterator for the collection. Allows list comprehensions, etc diff --git a/cobbler/distros.py b/cobbler/distros.py index a6b4b14..a510fd0 100644 --- a/cobbler/distros.py +++ b/cobbler/distros.py @@ -8,6 +8,7 @@ and initrd files """ class Distros(Collection): _item_factory = distro.Distro + _filename = "/var/lib/cobbler/distros" def remove(self,name): """ diff --git a/cobbler/item.py b/cobbler/item.py index e30ada5..0d43d9f 100644 --- a/cobbler/item.py +++ b/cobbler/item.py @@ -3,6 +3,12 @@ An Item is a serializable thing that can appear in a Collection """ class Item: + """ + constructor must be of format: + def __init__(self,seed_data) + where seed_data is a hash of argument_name/value pairs + see profile.py for example + """ def set_name(self,name): """ @@ -35,3 +41,5 @@ class Item: items been set, are they free of conflicts, etc. """ return False + + diff --git a/cobbler/profile.py b/cobbler/profile.py index 19db0e8..09c91bf 100644 --- a/cobbler/profile.py +++ b/cobbler/profile.py @@ -1,7 +1,6 @@ class Profile(Item): - def __init__(self,api,seed_data): - self.api = api + def __init__(self,seed_data): self.name = None self.distro = None # a name, not a reference self.kickstart = None diff --git a/cobbler/profiles.py b/cobbler/profiles.py index c73e8c3..9fb2d92 100644 --- a/cobbler/profiles.py +++ b/cobbler/profiles.py @@ -11,6 +11,7 @@ additional options, with client-side defaults (not kept here). """ class Profiles(Collection): _item_factory = profile.Profile + _filename = "/var/lib/cobbler/profiles" def remove(self,name): """ diff --git a/cobbler/serializable.py b/cobbler/serializable.py new file mode 100644 index 0000000..e03b88f --- /dev/null +++ b/cobbler/serializable.py @@ -0,0 +1,12 @@ + +class Serializable: + + def filename(): + return None + + def from_datastruct(datastruct): + raise "not implemented" + + def to_datastruct(): + raise "not implemented" + diff --git a/cobbler/serializer.py b/cobbler/serializer.py new file mode 100644 index 0000000..847be6e --- /dev/null +++ b/cobbler/serializer.py @@ -0,0 +1,19 @@ +# Michael DeHaan <mdehaan@redhat.com> + +import api +import util + +def serialize(obj): + fd = open(obj.filename(),"w+") + datastruct = obj.to_datastruct() + yaml = syck.dump(datastruct) + fd.write(yaml) + fd.close() + return True + +def deserialize(obj): + fd = open(obj.filename(),"r") + data = fd.read() + datastruct = yaml.load(data) + fd.close() + return obj.from_datastruct(datastruct) diff --git a/cobbler/serializer_tools.py b/cobbler/serializer_tools.py deleted file mode 100644 index 7c32ce5..0000000 --- a/cobbler/serializer_tools.py +++ /dev/null @@ -1,218 +0,0 @@ -# Abstracts out the config file format/access and holds -# reasonable default settings. -# -# Michael DeHaan <mdehaan@redhat.com> - -import api -import util -from msg import * -import distros -import profiles -import systems - -import weakref -import syck # pysyck > 0.61, so it has dump() -import os -import traceback - -global_settings_file = "/etc/cobbler.conf" -global_state_file = "/var/lib/cobbler/cobbler.conf" - - -class BootConfig: - - __instance - - def __new__(typ, *args, **kwargs): - if __instance is None: - __instance = object.__new__(type,*args,**kwargs) - return __instance - - def __init__(self): - """ - Constructor. This class maintains both the logical - configuration for Cobbler and the file representation thereof. - Creating the config object only loads the default values, - users of this class need to call deserialize() to load config - file values. See cobbler.py for how the CLI does it. - """ - self.settings_file = global_settings_file - self.state_file = global_state_file - self.set_defaults() - self.clear() - - def files_exist(self): - """ - Returns whether the config files exist. - """ - return os.path.exists(self.settings_file) and os.path.exists(self.state_file) - - def clear(self): - """ - Establish an empty list of profiles distros, and systems. - """ - profiles.clear() - distros.clear() - systems.clear() - - def set_defaults(self): - """ - Set some reasonable defaults in case no values are available - """ - self.server = "localhost" - self.tftpboot = "/tftpboot" - self.dhcpd_conf = "/etc/dhcpd.conf" - self.tftpd_conf = "/etc/xinetd.d/tftp" - self.pxelinux = "/usr/lib/syslinux/pxelinux.0" - self.tftpd_bin = "/usr/sbin/in.tftpd" - self.dhcpd_bin = "/usr/sbin/dhcpd" - self.httpd_bin = "/usr/sbin/httpd" - self.kernel_options = "append devfs=nomount ramdisk_size=16438 lang= vga=788 ksdevice=eth0" #initrd and ks added programmatically - - def get_profiles(self): - """ - Access the current profiles list - """ - return self.profiles - - def get_distros(self): - """ - Access the current distros list - """ - return self.distros - - def get_systems(self): - """ - Access the current systems list - """ - return self.systems - - def config_to_hash(self): - """ - Save all global config options in hash form (for serialization) - """ - data = {} - data["server"] = self.server - data['tftpboot'] = self.tftpboot - data['dhcpd_conf'] = self.dhcpd_conf - data['tftpd_conf'] = self.tftpd_conf - data['pxelinux'] = self.pxelinux - data['tftpd_bin'] = self.tftpd_bin - data['dhcpd_bin'] = self.dhcpd_bin - data['httpd_bin'] = self.httpd_bin - data['kernel_options'] = self.kernel_options - return data - - def config_from_hash(self,hash): - """ - Load all global config options from hash form (for deserialization) - """ - try: - self.server = hash['server'] - self.tftpboot = hash['tftpboot'] - self.dhcpd_conf = hash['dhcpd_conf'] - self.tftpd_conf = hash['tftpd_conf'] - self.pxelinux = hash['pxelinux'] - self.tftpd_bin = hash['tftpd_bin'] - self.dhcpd_bin = hash['dhcpd_bin'] - self.httpd_bin = hash['httpd_bin'] - self.kernel_options = hash['kernel_options'] - except: - print "WARNING: config file error: %s" % (self.settings_file) - self.set_defaults() - - def to_hash(self,is_etc): - """ - Convert all items cobbler knows about to a nested hash. - There are seperate hashes for the /etc and /var portions. - """ - world = {} - if is_etc: - world['config'] = self.config_to_hash() - else: - world['distros'] = self.get_distros().to_datastruct() - world['profiles'] = self.get_profiles().to_datastruct() - world['systems'] = self.get_systems().to_datastruct() - return world - - - def from_hash(self,hash,is_etc): - """ - Convert a hash representation of a cobbler to 'reality' - There are seperate hashes for the /etc and /var portions. - """ - if is_etc: - self.config_from_hash(hash['config']) - else: - distros.from_datastruct(hash['distros']) - profiles.from_datastruct(hash['profiles']) - systems.from_datastruct(hash['systems']) - - # ------------------------------------------------------ - # we don't care about file formats until below this line - - def serialize(self): - """ - Save everything to the config file. - This goes through an intermediate data format so we - could use YAML later if we wanted. - """ - - settings = None - state = None - - # ------ - # dump internal state (distros, profiles, systems...) into /var/lib/... - # /etc is not serialized, it's packaged. - - if not os.path.isdir(os.path.dirname(self.state_file)): - dirname = os.path.dirname(self.state_file) - if dirname != "": - os.makedirs(os.path.dirname(self.state_file)) - try: - state = open(self.state_file,"w+") - except: - runtime.set_error(m("cant_create: %s" % self.state_file)) - return False - data = self.to_hash(False) - state.write(syck.dump(data)) - - # all good - return True - - def deserialize(self): - """ - Load everything from the config file. - This goes through an intermediate data structure format so we - could use YAML later if we wanted. - """ - - # ----- - # load global config (pathing, urls, etc)... - try: - settings = syck.load(open(self.settings_file,"r").read()) - if settings is not None: - self.from_hash(settings,True) - else: - self.last_error = m("parse_error") - raise Exception("parse_error") - except: - runtime.set_error("parse_error") - raise Exception("parse_error") - - # ----- - # load internal state(distros, systems, profiles...) - try: - state = syck.load(open(self.state_file,"r").read()) - if state is not None: - self.from_hash(state,False) - else: - self.last_error = m("parse_error2") - raise Exception("parse_error2") - except: - runtime.set_error("parse_error2") - raise Exception("parse_error2") - - # all good - return True - diff --git a/cobbler/settings.py b/cobbler/settings.py new file mode 100644 index 0000000..2a7553f --- /dev/null +++ b/cobbler/settings.py @@ -0,0 +1,34 @@ +""" +Cobbler app-wide settings +""" + +class Settings(Serializable) + + def filename(self): + return "/var/lib/cobbler/settings" + + def __init__(self): + + self._attributes = { + "httpd_bin" : "/usr/sbin/httpd", + "pxelinux" : "/usr/lib/syslinux/pxelinux.0", + "dhcpd_conf" : "/etc/dhcpd.conf", + "tftpd_bin" : "/usr/sbin/in.tftpd", + "server" : "localhost", + "dhcpd_bin" : "/usr/sbin/dhcpd", + "kernel_options" : "append devfs=nomount ramdisk_size=16438 lang= vga=788 ksdevice=eth0", + "tftpd_conf" : "/etc/xinetd.d/tftp", + "tftpboot" : "/tftpboot", + } + + def to_datastruct(): + return self._attributes() + + def from_datastruct(datastruct): + self._attributes = datastruct + + def __getattr__(self,name): + if name in self._attributes: + return self._attributes[name] + else: + raise AttributeError, name diff --git a/cobbler/sync.py b/cobbler/sync.py index 31ab481..c768b57 100644 --- a/cobbler/sync.py +++ b/cobbler/sync.py @@ -2,7 +2,6 @@ # # Michael DeHaan <mdehaan@redhat.com> -import api import config import os @@ -21,8 +20,13 @@ Handles conversion of internal state to the tftpboot tree layout class BootSync: - def __init__(self): - self.verbose = True + def __init__(self,config): + self.verbose = True + self.config = config + self.distros = config.distros + self.profiles = config.profiles + self.systems = config.systems + self.settings = config.settings def sync(self,dry_run=False,verbose=True): @@ -48,7 +52,7 @@ class BootSync: """ Copy syslinux to the configured tftpboot directory """ - self.copy(config.config().pxelinux, os.path.join(config.config().tftpboot, "pxelinux.0")) + self.copy(self.settings.pxelinux, os.path.join(self.settings.tftpboot, "pxelinux.0")) def configure_httpd(self): """ @@ -59,7 +63,7 @@ class BootSync: self.sync_log(m("no_httpd")) return f = self.open_file("/etc/httpd/conf.d/cobbler.conf","w+") - config = """ + config_data = """ # # This configuration file allows 'cobbler' boot info # to be accessed over HTTP in addition to PXE. @@ -71,8 +75,8 @@ class BootSync: Allow from all </Directory> """ - config.replace("/tftpboot",config.config().tftpboot) - self.tee(f, config) + config_data.replace("/tftpboot",self.settings.tftpboot) + self.tee(f, config_data) self.close_file(f) def clean_trees(self): @@ -80,7 +84,7 @@ class BootSync: Delete any previously built pxelinux.cfg tree and xen tree info. """ for x in ["pxelinux.cfg","images","systems","distros","profiles","kickstarts"]: - path = os.path.join(config.config().tftpboot,x) + path = os.path.join(self.settings.tftpboot,x) self.rmtree(path, True) self.mkdir(path) @@ -93,8 +97,8 @@ class BootSync: mounted. """ # copy is a 4-letter word but tftpboot runs chroot, thus it's required. - distros = os.path.join(config.config().tftpboot, "images") - for d in distros.get_distros(): + distros = os.path.join(self.settings.tftpboot, "images") + for d in self.distros: distro_dir = os.path.join(distros,d.name) self.mkdir(distro_dir) kernel = utils.find_kernel(d.kernel) # full path @@ -123,12 +127,12 @@ class BootSync: # these are served by either NFS, Apache, or some ftpd, so we don't need to copy them # it's up to the user to make sure they are nicely served by their URLs - for g in profiles.get_profiles(): + for g in self.profiles: self.sync_log("mirroring any local kickstarts: %s" % g.name) kickstart_path = utils.find_kickstart(g.kickstart) if kickstart_path and os.path.exists(kickstart_path): # the input is an *actual* file, hence we have to copy it - copy_path = os.path.join(config.config().tftpboot, "kickstarts", g.name) + copy_path = os.path.join(self.settings.tftpboot, "kickstarts", g.name) self.mkdir(copy_path) dest = os.path.join(copy_path, "ks.cfg") try: @@ -147,47 +151,44 @@ class BootSync: print "building trees..." # create pxelinux.cfg under tftpboot # and file for each MAC or IP (hex encoded 01-XX-XX-XX-XX-XX-XX) - systems = systems.get_systems() - profiles = profiles.get_profiles() - distros = distros.get_distros() - for d in distros: + for d in self.distros: self.sync_log("processing distro: %s" % d.name) # TODO: add check to ensure all distros have profiles (=warning) - filename = os.path.join(config.config().tftpboot,"distros",d.name) + filename = os.path.join(self.settings.tftpboot,"distros",d.name) d.kernel_options = self.blend_kernel_options(( - config.config().kernel_options, + self.settings.kernel_options, d.kernel_options )) self.write_distro_file(filename,d) - for p in profiles: + for p in self.profiles: self.sync_log("processing profile: %s" % p.name) # TODO: add check to ensure all profiles have distros (=error) # TODO: add check to ensure all profiles have systems (=warning) - filename = os.path.join(config.config().tftpboot,"profiles",p.name) - distro = distros.get_distros().find(p.distro) + filename = os.path.join(self.settings.tftpboot,"profiles",p.name) + distro = self.distros.find(p.distro) if distro is not None: p.kernel_options = self.blend_kernel_options(( - config.config().kernel_options, + self.settings.kernel_options, distro.kernel_options, p.kernel_options )) self.write_profile_file(filename,p) - for system in systems: + for system in self.systems: self.sync_log("processing system: %s" % system.name) - profile = profiles.find(system.profile) + profile = self.profiles.find(system.profile) if profile is None: runtime.set_error("orphan_profile2") raise "error" - distro = distros.find(profile.distro) + distro = self.distros.find(profile.distro) if distro is None: runtime.set_error("orphan_system2") raise "error" f1 = self.get_pxelinux_filename(system.name) - f2 = os.path.join(config.config().tftpboot, "pxelinux.cfg", f1) - f3 = os.path.join(config.config().tftpboot, "systems", f1) + f2 = os.path.join(self.settings.tftpboot, "pxelinux.cfg", f1) + f3 = os.path.join(self.settings.tftpboot, "systems", f1) self.write_pxelinux_file(f2,system,profile,distro) self.write_system_file(f3,system) @@ -228,7 +229,7 @@ class BootSync: self.tee(fd,"label linux\n") self.tee(fd," kernel %s\n" % kernel_path) kopts = self.blend_kernel_options(( - config.config().kernel_options, + self.settings.kernel_options, profile.kernel_options, distro.kernel_options, system.kernel_options @@ -238,7 +239,7 @@ class BootSync: # if kickstart path is local, we've already copied it into # the HTTP mirror, so make it something anaconda can get at if kickstart_path.startswith("/"): - kickstart_path = "http://%s/cobbler/kickstarts/%s/ks.cfg" % (config.config().server, profile.name) + kickstart_path = "http://%s/cobbler/kickstarts/%s/ks.cfg" % self.settings.server, profile.name) nextline = nextline + " ks=%s" % kickstart_path self.tee(fd, nextline) self.close_file(fd) @@ -265,7 +266,7 @@ class BootSync: # if kickstart path is local, we've already copied it into # the HTTP mirror, so make it something anaconda can get at if profile.kickstart and profile.kickstart.startswith("/"): - profile.kickstart = "http://%s/cobbler/kickstarts/%s/ks.cfg" % (config.config().server, profile.name) + profile.kickstart = "http://%s/cobbler/kickstarts/%s/ks.cfg" % (self.settings.server, profile.name) self.tee(fd,syck.dump(profile.to_datastruct())) self.close_file(fd) @@ -385,8 +386,3 @@ class BootSync: # end result is a new fragment of a kernel options string return " ".join(results) -__instance = BootSync() - -def bootsync() - return __instance - diff --git a/cobbler/systems.py b/cobbler/systems.py index 47dc042..9c48c00 100644 --- a/cobbler/systems.py +++ b/cobbler/systems.py @@ -9,6 +9,7 @@ they belong to. """ class Systems(Collection): _item_factory = system.System + _filename = "/var/lib/cobbler/systems" def remove(self,name): """ diff --git a/cobbler/util.py b/cobbler/util.py index ed18869..a851440 100644 --- a/cobbler/util.py +++ b/cobbler/util.py @@ -9,10 +9,18 @@ import re import socket import glob import weakref +import subprocess re_kernel = re.compile(r'vmlinuz-(\d+)\.(\d+)\.(\d+)-(.*)') re_initrd = re.compile(r'initrd-(\d+)\.(\d+)\.(\d+)-(.*).img') +def get_host_ip(ip): + handle = subprocess.Popen("/usr/bin/gethostip %s" % ip, + shell=True + stdout=PIPE) + out = handle.stdout + results = out.read() + return results.split(" ")[-1] def find_system_identifier(self,strdata): """ diff --git a/cobbler/~api.py b/cobbler/~api.py deleted file mode 100644 index e69de29..0000000 --- a/cobbler/~api.py +++ /dev/null diff --git a/tests/tests.py b/tests/tests.py index 34b66d8..1db0ba3 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -57,21 +57,21 @@ class BootTest(unittest.TestCase): self.assertTrue(distro.set_name("testdistro0")) self.assertTrue(distro.set_kernel(self.fk_kernel)) self.assertTrue(distro.set_initrd(self.fk_initrd)) - self.assertTrue(self.api.get_distros().add(distro)) - self.assertTrue(self.api.get_distros().find("testdistro0")) + self.assertTrue(self.api.distros().add(distro)) + self.assertTrue(self.api.distros().find("testdistro0")) profile = self.api.new_profile() self.assertTrue(profile.set_name("testprofile0")) self.assertTrue(profile.set_distro("testdistro0")) self.assertTrue(profile.set_kickstart(FAKE_KICKSTART)) - self.assertTrue(self.api.get_profiles().add(profile)) - self.assertTrue(self.api.get_profiles().find("testprofile0")) + self.assertTrue(self.api.profiles().add(profile)) + self.assertTrue(self.api.profiles().find("testprofile0")) system = self.api.new_system() self.assertTrue(system.set_name(self.hostname)) self.assertTrue(system.set_profile("testprofile0")) - self.assertTrue(self.api.get_systems().add(system)) - self.assertTrue(self.api.get_systems().find(self.hostname)) + self.assertTrue(self.api.systems().add(system)) + self.assertTrue(self.api.systems().find(self.hostname)) class Utilities(BootTest): @@ -122,24 +122,24 @@ class Additions(BootTest): self.assertTrue(distro.set_name("testdistro2")) self.assertFalse(distro.set_kernel("filedoesntexist")) self.assertTrue(distro.set_initrd(self.fk_initrd)) - self.assertFalse(self.api.get_distros().add(distro)) - self.assertFalse(self.api.get_distros().find("testdistro2")) + self.assertFalse(self.api.distros().add(distro)) + self.assertFalse(self.api.distros().find("testdistro2")) def test_invalid_distro_non_referenced_initrd(self): distro = self.api.new_distro() self.assertTrue(distro.set_name("testdistro3")) self.assertTrue(distro.set_kernel(self.fk_kernel)) self.assertFalse(distro.set_initrd("filedoesntexist")) - self.assertFalse(self.api.get_distros().add(distro)) - self.assertFalse(self.api.get_distros().find("testdistro3")) + self.assertFalse(self.api.distros().add(distro)) + self.assertFalse(self.api.distros().find("testdistro3")) def test_invalid_profile_non_referenced_distro(self): profile = self.api.new_profile() self.assertTrue(profile.set_name("testprofile11")) self.assertFalse(profile.set_distro("distrodoesntexist")) self.assertTrue(profile.set_kickstart(FAKE_KICKSTART)) - self.assertFalse(self.api.get_profiles().add(profile)) - self.assertFalse(self.api.get_profiles().find("testprofile2")) + self.assertFalse(self.api.profiles().add(profile)) + self.assertFalse(self.api.profiles().find("testprofile2")) def test_invalid_profile_kickstart_not_url(self): profile = self.api.new_profile() @@ -147,8 +147,8 @@ class Additions(BootTest): self.assertTrue(profile.set_distro("testdistro0")) self.assertFalse(profile.set_kickstart("kickstartdoesntexist")) # since kickstarts are optional, you can still add it - self.assertTrue(self.api.get_profiles().add(profile)) - self.assertTrue(self.api.get_profiles().find("testprofile12")) + self.assertTrue(self.api.profiles().add(profile)) + self.assertTrue(self.api.profiles().find("testprofile12")) # now verify the other kickstart forms would still work self.assertTrue(profile.set_kickstart("http://bar")) self.assertTrue(profile.set_kickstart("ftp://bar")) @@ -180,67 +180,67 @@ class Additions(BootTest): self.assertFalse(profile.set_xen_paravirt(11)) # each item should be 'true' now, so we can add it # since the failed items don't affect status - self.assertTrue(self.api.get_profiles().add(profile)) + self.assertTrue(self.api.profiles().add(profile)) def test_invalid_system_bad_name_host(self): system = self.api.new_system() name = "hostnamewontresolveanyway" self.assertFalse(system.set_name(name)) self.assertTrue(system.set_profile("testprofile0")) - self.assertFalse(self.api.get_systems().add(system)) - self.assertFalse(self.api.get_systems().find(name)) + self.assertFalse(self.api.systems().add(system)) + self.assertFalse(self.api.systems().find(name)) def test_system_name_is_a_MAC(self): system = self.api.new_system() name = "00:16:41:14:B7:71" self.assertTrue(system.set_name(name)) self.assertTrue(system.set_profile("testprofile0")) - self.assertTrue(self.api.get_systems().add(system)) - self.assertTrue(self.api.get_systems().find(name)) + self.assertTrue(self.api.systems().add(system)) + self.assertTrue(self.api.systems().find(name)) def test_system_name_is_an_IP(self): system = self.api.new_system() name = "192.168.1.54" self.assertTrue(system.set_name(name)) self.assertTrue(system.set_profile("testprofile0")) - self.assertTrue(self.api.get_systems().add(system)) - self.assertTrue(self.api.get_systems().find(name)) + self.assertTrue(self.api.systems().add(system)) + self.assertTrue(self.api.systems().find(name)) def test_invalid_system_non_referenced_profile(self): system = self.api.new_system() self.assertTrue(system.set_name(self.hostname)) self.assertFalse(system.set_profile("profiledoesntexist")) - self.assertFalse(self.api.get_systems().add(system)) + self.assertFalse(self.api.systems().add(system)) class Deletions(BootTest): def test_invalid_delete_profile_doesnt_exist(self): - self.assertFalse(self.api.get_profiles().remove("doesnotexist")) + self.assertFalse(self.api.profiles().remove("doesnotexist")) def test_invalid_delete_profile_would_orphan_systems(self): self.make_basic_config() - self.assertFalse(self.api.get_profiles().remove("testprofile0")) + self.assertFalse(self.api.profiles().remove("testprofile0")) def test_invalid_delete_system_doesnt_exist(self): - self.assertFalse(self.api.get_systems().remove("doesnotexist")) + self.assertFalse(self.api.systems().remove("doesnotexist")) def test_invalid_delete_distro_doesnt_exist(self): - self.assertFalse(self.api.get_distros().remove("doesnotexist")) + self.assertFalse(self.api.distros().remove("doesnotexist")) def test_invalid_delete_distro_would_orphan_profile(self): self.make_basic_config() - self.assertFalse(self.api.get_distros().remove("testdistro0")) + self.assertFalse(self.api.distros().remove("testdistro0")) def test_working_deletes(self): self.api.clear() self.make_basic_config() - self.assertTrue(self.api.get_systems().remove(self.hostname)) + self.assertTrue(self.api.systems().remove(self.hostname)) self.api.serialize() - self.assertTrue(self.api.get_profiles().remove("testprofile0")) - self.assertTrue(self.api.get_distros().remove("testdistro0")) - self.assertFalse(self.api.get_systems().find(self.hostname)) - self.assertFalse(self.api.get_profiles().find("testprofile0")) - self.assertFalse(self.api.get_distros().find("testdistro0")) + self.assertTrue(self.api.profiles().remove("testprofile0")) + self.assertTrue(self.api.distros().remove("testdistro0")) + self.assertFalse(self.api.systems().find(self.hostname)) + self.assertFalse(self.api.profiles().find("testprofile0")) + self.assertFalse(self.api.distros().find("testdistro0")) class TestCheck(BootTest): @@ -271,9 +271,9 @@ class TestListings(BootTest): # check to see if the collection listings output something. # this is a minimal check, mainly for coverage, not validity self.make_basic_config() - self.assertTrue(len(self.api.get_systems().printable()) > 0) - self.assertTrue(len(self.api.get_profiles().printable()) > 0) - self.assertTrue(len(self.api.get_distros().printable()) > 0) + self.assertTrue(len(self.api.systems().printable()) > 0) + self.assertTrue(len(self.api.profiles().printable()) > 0) + self.assertTrue(len(self.api.distros().printable()) > 0) class TestCLIBasic(BootTest): |