From 018d57abccb1ad0310792a3018cad652c2bc70c6 Mon Sep 17 00:00:00 2001 From: Mihai Ibanescu Date: Thu, 4 May 2006 14:58:31 -0400 Subject: Merged Michael's changes with mine --- Makefile | 22 +++--- TODO | 4 +- bootconf.man | 217 ---------------------------------------------------- cobbler.pod | 4 +- cobbler/IPy.py | 120 ++++++++++++++--------------- cobbler/api.py | 81 ++++++++++---------- cobbler/check.py | 18 ++--- cobbler/cobbler | 6 ++ cobbler/cobbler.py | 32 ++++---- cobbler/config.py | 42 +++++----- cobbler/msg.py | 8 +- cobbler/sync.py | 44 +++++------ cobbler/util.py | 24 +++--- examples/example_ks | 4 +- tests/tests.py | 40 +++------- 15 files changed, 221 insertions(+), 445 deletions(-) delete mode 100644 bootconf.man create mode 100755 cobbler/cobbler diff --git a/Makefile b/Makefile index d8174f8..8e917c0 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,21 @@ -all: clean test manpage install - clean: - @rm -f *.gz *.rpm MANIFEST - @rm -rf cobbler-* dist build + \rm -f cobbler*.gz cobbler*.rpm MANIFEST + \rm -rf cobbler-* dist build manpage: - pod2man --center="cobbler" --release="" cobbler.pod > cobbler.1 - @\rm -f cobbler.1.gz - gzip cobbler.1 + pod2man --center="cobbler" --release="" cobbler.pod | gzip -c > cobbler.1.gz test: python tests/tests.py -install: clean +install: clean manpage python setup.py sdist cp dist/*.gz . - rpmbuild --define "_topdir %(pwd)" --define "_builddir %{_topdir}" --define "_rpmdir %{_topdir}" --define "_srcrpmdir %{_topdir}" --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' --define "_specdir %{_topdir}" --define "_sourcedir %{_topdir}" -ba cobbler.spec - + rpmbuild --define "_topdir %(pwd)" \ + --define "_builddir %{_topdir}" \ + --define "_rpmdir %{_topdir}" \ + --define "_srcrpmdir %{_topdir}" \ + --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \ + --define "_specdir %{_topdir}" \ + --define "_sourcedir %{_topdir}" \ + -ba cobbler.spec diff --git a/TODO b/TODO index 8c18700..b333c8d 100644 --- a/TODO +++ b/TODO @@ -7,13 +7,13 @@ I - Manpage (in progress) .. continuously... distro add [--kickstart] optionally as alternative to [--kernel=] and [--initrd=]. If so, groups can inherit kickstart info from the group (can and must if the distro has a kickstart specified). - - Don't require root, check for permissions on dirpaths we touch. + - Don't require root, check for permissions on dirpaths we touch. - Support pxelinux's default directory - Subnet creation shorthand ... (system objects that match multiples?) - MAC ranges for Xen (how best to do this? shelve this for now). - If there is a parse error in /etc/cobbler.conf and the user deletes that file, fix the bug that also deletes the file in /var. - + LEGEND T = DONE. SOME TESTING STILL TBA. diff --git a/bootconf.man b/bootconf.man deleted file mode 100644 index 7073b62..0000000 --- a/bootconf.man +++ /dev/null @@ -1,217 +0,0 @@ -.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32 -.\" -.\" Standard preamble: -.\" ======================================================================== -.de Sh \" Subsection heading -.br -.if t .Sp -.ne 5 -.PP -\fB\\$1\fR -.PP -.. -.de Sp \" Vertical space (when we can't use .PP) -.if t .sp .5v -.if n .sp -.. -.de Vb \" Begin verbatim text -.ft CW -.nf -.ne \\$1 -.. -.de Ve \" End verbatim text -.ft R -.fi -.. -.\" Set up some character translations and predefined strings. \*(-- will -.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left -.\" double quote, and \*(R" will give a right double quote. | will give a -.\" real vertical bar. \*(C+ will give a nicer C++. Capital omega is used to -.\" do unbreakable dashes and therefore won't be available. \*(C` and \*(C' -.\" expand to `' in nroff, nothing in troff, for use with C<>. -.tr \(*W-|\(bv\*(Tr -.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' -.ie n \{\ -. ds -- \(*W- -. ds PI pi -. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch -. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch -. ds L" "" -. ds R" "" -. ds C` "" -. ds C' "" -'br\} -.el\{\ -. ds -- \|\(em\| -. ds PI \(*p -. ds L" `` -. ds R" '' -'br\} -.\" -.\" If the F register is turned on, we'll generate index entries on stderr for -.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index -.\" entries marked with X<> in POD. Of course, you'll have to process the -.\" output yourself in some meaningful fashion. -.if \nF \{\ -. de IX -. tm Index:\\$1\t\\n%\t"\\$2" -.. -. nr % 0 -. rr F -.\} -.\" -.\" For nroff, turn off justification. Always turn off hyphenation; it makes -.\" way too many mistakes in technical documents. -.hy 0 -.if n .na -.\" -.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). -.\" Fear. Run. Save yourself. No user-serviceable parts. -. \" fudge factors for nroff and troff -.if n \{\ -. ds #H 0 -. ds #V .8m -. ds #F .3m -. ds #[ \f1 -. ds #] \fP -.\} -.if t \{\ -. ds #H ((1u-(\\\\n(.fu%2u))*.13m) -. ds #V .6m -. ds #F 0 -. ds #[ \& -. ds #] \& -.\} -. \" simple accents for nroff and troff -.if n \{\ -. ds ' \& -. ds ` \& -. ds ^ \& -. ds , \& -. ds ~ ~ -. ds / -.\} -.if t \{\ -. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" -. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' -. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' -. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' -. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' -. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' -.\} -. \" troff and (daisy-wheel) nroff accents -.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' -.ds 8 \h'\*(#H'\(*b\h'-\*(#H' -.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] -.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' -.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' -.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] -.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] -.ds ae a\h'-(\w'a'u*4/10)'e -.ds Ae A\h'-(\w'A'u*4/10)'E -. \" corrections for vroff -.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' -.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' -. \" for low resolution devices (crt and lpr) -.if \n(.H>23 .if \n(.V>19 \ -\{\ -. ds : e -. ds 8 ss -. ds o a -. ds d- d\h'-1'\(ga -. ds D- D\h'-1'\(hy -. ds th \o'bp' -. ds Th \o'LP' -. ds ae ae -. ds Ae AE -.\} -.rm #[ #] #H #V #F C -.\" ======================================================================== -.\" -.IX Title "BOOTCONF 1" -.TH BOOTCONF 1 "2006-04-05" "perl v5.8.8" "User Contributed Perl Documentation" -bootconf \- simple configuration of \s-1PXE\s0 boot + kickstart environments -.SH "SYNOPSIS" -.IX Header "SYNOPSIS" -bootconf command [subcommand] [\-\-arg1=] [\-\-arg2=] -See '\s-1INSTRUCTIONS\s0'. -.SH "DESCRIPTION" -.IX Header "DESCRIPTION" -Configuration of a boot server involves a lot of things. Perhaps too many. These items include a tftpd server, a dhcpd server, syslinux, and a lot of copying files around and creating other config files. Bootconf makes these things simpler. -.PP -bootconf requires root access. -.SH "INSTRUCTIONS" -.IX Header "INSTRUCTIONS" -\&\fBbefore you start...\fR -.PP -First install dhcpd, tftpd, and syslinux. -You'll also need \s-1FTP\s0, \s-1HTTP\s0, or \s-1NFS\s0 to serve kickstarts (if you want them) -And you'll also have to edit dhcpd.conf for your particular \s-1DHCP\s0 environment. -Yes, \s-1DHCP\s0 is a required piece. -.PP -.Vb 3 -\& yum install dhcp tftp-server syslinux -\& ... -\& vi /etc/dhcpd.conf -.Ve -.PP -\&\fBbootconf check\fR -.PP -This verifies that everything you need to set up is set up. -This will mention any missing services and obvious configuration errors. -Errors? Correct any problems reported, then run check until there are none. -.PP -\&\fBbootconf distro add \-\-name=distro_name \-\-kernel=path \-\-initrd=path\fR -.PP -Defines a distribution as a matched pair of a kernel and an initrd, and -gives it a name. This could be 'fc5\-i386' or 'foosball\-73', it's your name. -.PP -\&\fBbootconf group add \-\-name=group_name \-\-kickstart=path \-\-distro=distro_name\fR -.PP -Defines a provisioning group, which is a distro (you did define a distro, right?) and a kickstart. Kickstarts are served up by URLs, though this needs to be a file. The distro parameter is whatever named you used earlier. -.PP -\&\fBbootconf system add \-\-name=ip|mac|hostname \-\-group=group_name\fR -.PP -Defines a system. A system can't have a free form name, it has to be a hostname that resolves to an \s-1IP\s0, a \s-1MAC\s0 address, or an actual \s-1IP\s0. The group parameter is the group name you used with \*(L"group add\*(R". -.PP -\&\fBbootconf distro list\fR -.PP -Gives a list of distributions that are currently configured. If you specified any kernels or initrd's by directories instead of full paths, it will indicate which ones are currently picked. -.PP -\&\fBbootconf group list\fR -.PP -Gives a list of groups that are currently configured. -.PP -\&\fBbootconf system list\fR -.PP -Gives a list of the sytems that are currently configured. -.PP -\&\fBbootconf distro remove \-\-name=distro_name\fR -.PP -Deletes a distro from the stored configuration. You can't delete a distro if any groups reference that distro, you'd have to delete all of the systems using that distro first. Deleting a distro just means that you no longer want it configured for booting, it does not delete any files. -.PP -\&\fBbootconf group remove \-\-name=group_name\fR -.PP -Deletes a group from the stored configuration. You can't delete a group if any systems reference it. Does not actually delete any kickstart files. -.PP -\&\fBbootconf system remove \-\-name=system_name\fR -.PP -Deletes a system from the stored configuration. You can always delete a system. -.PP -\&\fBbootconf sync [\-\-dryrun]\fR -.PP -Applies the stored configuration to the system. If you have any configuration errors, bootconf sync will ask you to run 'bootconf check' and fix them first. -.PP -Usage of the dryrun option will show you the pending changes without actually making them. -.SH "CONFIGURATION_FILES" -.IX Header "CONFIGURATION_FILES" -bootconf uses /etc/bootconf.conf to store it's internal state. You may want to manually edit the global options at the top of the file for some reason. If you do so, run 'bootconf check' again to ensure there are no errors introduced from this. In a worse case scenario, you can delete the file and the next run of bootconf will recreate it. -.SH "BUGS" -.IX Header "BUGS" -It's a new product, there could be one. Somewhere. -.SH "EXIT_STATUS" -.IX Header "EXIT_STATUS" -bootconf returns a zero for success and non-zero for failure. -.SH "AUTHOR" -.IX Header "AUTHOR" -Michael DeHaan diff --git a/cobbler.pod b/cobbler.pod index 12b8f29..9d7ba72 100644 --- a/cobbler.pod +++ b/cobbler.pod @@ -34,7 +34,7 @@ B --kernel= --initrd= [--kopts= [--kickstart=] [--kopts=] [--xen-name=] [--xen-file-size=] [--xen-ram=] +B [--kickstart=] [--kopts=] [--xen-name=] [--xen-file-size=] [--xen-ram=] Defines a provisioning profile, which is a distro, some other optional parameters, and a name for the profile. Almost always you will want to specify a kickstart file. Kickstarts can be nfs://, http://, or ftp:// -- or an absolute path. For file paths, cobbler will copy the kickstarts and serve them up as http. @@ -70,7 +70,7 @@ Configurations are saved in /var/cobbler/cobbler.conf and are not applied until =head1 PXE EXAMPLE -It is easier to understand cobbler once having gone through some examples. +It is easier to understand cobbler once having gone through some examples. B diff --git a/cobbler/IPy.py b/cobbler/IPy.py index ac2b1c5..6f6af41 100644 --- a/cobbler/IPy.py +++ b/cobbler/IPy.py @@ -94,7 +94,7 @@ WantPrefixLen >>> ip.WantPrefixLen = 3 >>> print ip 10.0.0.0-10.0.0.0 - + Further Information might be available at http://c0re.jp/c0de/IPy/ @@ -108,7 +108,7 @@ TODO: * 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 + * update address type tables * first-last notation should be allowed for IPv6 * add IPv6 docstring examples * check better for negative parameters @@ -154,7 +154,7 @@ IPv6ranges = { '00000001' : 'UNASSIGNED', # 100::/8 '0000001' : 'NSAP', # 200::/7 '0000010' : 'IPX', # 400::/7 - '0000011' : 'UNASSIGNED', # 600::/7 + '0000011' : 'UNASSIGNED', # 600::/7 '00001' : 'UNASSIGNED', # 800::/5 '0001' : 'UNASSIGNED', # 1000::/4 '0010000000000000' : 'RESERVED', # 2000::/16 Reserved @@ -166,7 +166,7 @@ IPv6ranges = { '0010000000000010' : '6TO4', # 2002::/16 "6to4" [RFC3056] '0011111111111110' : '6BONE', # 3FFE::/16 6bone Testing [RFC2471] '0011111111111111' : 'RESERVED', # 3FFF::/16 Reserved - '010' : 'GLOBAL-UNICAST', # 4000::/3 + '010' : 'GLOBAL-UNICAST', # 4000::/3 '011' : 'UNASSIGNED', # 6000::/3 '100' : 'GEO-UNICAST', # 8000::/3 '101' : 'UNASSIGNED', # A000::/3 @@ -191,10 +191,10 @@ class IPint: 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 @@ -218,10 +218,10 @@ class IPint: 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) @@ -260,7 +260,7 @@ class IPint: raise ValueError, "last address should be larger than first" size = last - self.ip netbits = _count1Bits(size) - elif len(x) == 1: + elif len(x) == 1: x = data.split('/') # if no prefix is given use defaults if len(x) == 1: @@ -276,7 +276,7 @@ class IPint: (netmask, vers) = parseAddress(prefixlen) if vers != 4: raise ValueError, "netmask must be IPv4" - prefixlen = _netmaskToPrefixlen(netmask) + prefixlen = _netmaskToPrefixlen(netmask) elif len(x) > 2: raise ValueError, "only one '-' allowed in IP Address" else: @@ -296,8 +296,8 @@ class IPint: 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) - + 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. @@ -351,7 +351,7 @@ class IPint: """ if (self._ipversion == 4 and self._prefixlen == 32) or \ - (self._ipversion == 6 and self._prefixlen == 128): + (self._ipversion == 6 and self._prefixlen == 128): if self.NoPrefixForSingleIp: want = 0 if want == None: @@ -380,7 +380,7 @@ class IPint: # strHex 0x7F000001L 0x20010658022ACAFE0200C0FFFE8D08FA # strDec 2130706433 42540616829182469433547974687817795834 - def strBin(self, wantprefixlen = None): + def strBin(self, wantprefixlen = None): """Return a string representation as a binary value. >>> print IP('127.0.0.1').strBin() @@ -389,7 +389,7 @@ class IPint: if self._ipversion == 4: - bits = 32 + bits = 32 elif self._ipversion == 6: bits = 128 else: @@ -408,10 +408,10 @@ class IPint: >>> 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: @@ -450,16 +450,16 @@ class IPint: if self.WantPrefixLen == None and wantprefixlen == None: wantprefixlen = 1 - + if self._ipversion == 4: - ret = self.strFullsize(0) + 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): @@ -473,7 +473,7 @@ class IPint: if self.WantPrefixLen == None and wantprefixlen == None: wantprefixlen = 1 - + return intToIp(self.ip, self._ipversion).lower() + self._printPrefix(wantprefixlen) def strHex(self, wantprefixlen = None): @@ -530,9 +530,9 @@ class IPint: # this could be greatly improved if self._ipversion == 4: - iprange = IPv4ranges + iprange = IPv4ranges elif self._ipversion == 6: - iprange = IPv6ranges + iprange = IPv6ranges else: raise ValueError, "only IPv4 and IPv6 supported" @@ -596,7 +596,7 @@ class IPint: else: raise ValueError, "only IPv4 and IPv6 supported" - return 2L ** locallen + return 2L ** locallen def __len__(self): @@ -607,13 +607,13 @@ class IPint: # 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()) @@ -637,7 +637,7 @@ class IPint: return self.ip + long(key) - + def __contains__(self, item): """Called to implement membership test operators. @@ -686,7 +686,7 @@ class IPint: else: return 0 - + def __str__(self): """Dispatch to the prefered String Representation. @@ -706,7 +706,7 @@ class IPint: IP('10.0.0.0/24') """ - return("IPint('%s')" % (self.strCompressed(1))) + return("IPint('%s')" % (self.strCompressed(1))) def __cmp__(self, other): @@ -735,11 +735,11 @@ class IPint: # Im not really sure if this is "the right thing to do" if self._prefixlen < other.prefixlen(): - return (other.prefixlen() - self._prefixlen) + return (other.prefixlen() - self._prefixlen) elif self._prefixlen > other.prefixlen(): # Fixed bySamuel Krempp : - + # 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 @@ -752,17 +752,17 @@ class IPint: # 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 + 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 @@ -834,7 +834,7 @@ class IP(IPint): ['128.in-addr.arpa.'] >>> IP('128.0.0.0/7').reverseNames() ['128.in-addr.arpa.', '129.in-addr.arpa.'] - + """ if self._ipversion == 4: @@ -866,8 +866,8 @@ class IP(IPint): 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. @@ -886,7 +886,7 @@ class IP(IPint): s = self.strFullsize(0) s = s.split('.') s.reverse() - first_byte_index = int(4 - (self._prefixlen / 8)) + 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': @@ -919,7 +919,7 @@ class IP(IPint): 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) @@ -942,7 +942,7 @@ class IP(IPint): IP('10.0.0.0/8') """ - return("IP('%s')" % (self.strCompressed(1))) + return("IP('%s')" % (self.strCompressed(1))) def __add__(self, other): """Emulate numeric objects through network aggregation""" @@ -957,7 +957,7 @@ class IP(IPint): return other.__add__(self) else: ret = IP(self.int()) - ret._prefixlen = self.prefixlen() - 1 + ret._prefixlen = self.prefixlen() - 1 return ret def parseAddress(ipstr): @@ -986,7 +986,7 @@ def parseAddress(ipstr): return (ret, 4) else: return (ret, 6) - + if ipstr.find(':') != -1: # assume IPv6 if ipstr.find(':::') != -1: @@ -1010,7 +1010,7 @@ def parseAddress(ipstr): # 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('') @@ -1022,7 +1022,7 @@ def parseAddress(ipstr): for x in hextets: if len(x) < 4: x = ((4 - len(x)) * '0') + x - if int(x, 16) < 0 or int(x, 16) > 0xffff: + 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) @@ -1030,7 +1030,7 @@ def parseAddress(ipstr): 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('.') @@ -1064,9 +1064,9 @@ def intToIp(ip, version): if ip < 0: raise ValueError, "IPs can't be negative: %d" % (ip) - + ret = '' - if version == 4: + if version == 4: if ip > 0xffffffffL: raise ValueError, "IPv4 Addresses can't be larger than 0xffffffff: %s" % (hex(ip)) for l in range(4): @@ -1084,7 +1084,7 @@ def intToIp(ip, version): ret = ret[1:] else: raise ValueError, "only IPv4 and IPv6 supported" - + return ret; def _ipVersionToLen(version): @@ -1124,7 +1124,7 @@ _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.""" @@ -1142,7 +1142,7 @@ def _intToBin(val): # remove leading zeros while ret[0] == '0' and len(ret) > 1: ret = ret[1:] - return ret + return ret def _count1Bits(num): """Find the highest bit set to 1 in an integer.""" @@ -1165,12 +1165,12 @@ def _count0Bits(num): break num = num >> 1 ret += 1 - return ret + 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. @@ -1186,11 +1186,11 @@ def _checkPrefix(ip, prefixlen, version): # TODO: unify this v4/v6/invalid code in a function bits = _ipVersionToLen(version) - + if prefixlen < 0 or prefixlen > bits: return None - if ip == 0: + if ip == 0: zbits = bits + 1 else: zbits = _count0Bits(ip) @@ -1205,7 +1205,7 @@ def _checkNetmask(netmask, masklen): num = long(netmask) bits = masklen - + # remove zero bits at the end while (num & 1) == 0: num = num >> 1 @@ -1226,7 +1226,7 @@ def _checkNetaddrWorksWithPrefixlen(net, prefixlen, version): return 1 else: return 0 - + def _netmaskToPrefixlen(netmask): """Convert an Integer reprsenting a Netmask to an prefixlen. @@ -1251,7 +1251,7 @@ def _prefixlenToNetmask(prefixlen, version): elif prefixlen < 0: raise ValueError, "Prefixlen must be > 0" return ((2L< @@ -36,7 +36,6 @@ class BootAPI: self.config.serialize() except: traceback.print_exc() - pass if not self.config.files_exist(): self.config.serialize() @@ -57,7 +56,7 @@ class BootAPI: def get_profiles(self): """ - Return the current list of profiles + Return the current list of profiles """ return self.config.get_profiles() @@ -105,10 +104,10 @@ class BootAPI: def sync(self,dry_run=True): """ Take the values currently written to the configuration files in - /etc, and /var, and build out the information tree found in + /etc, and /var, and build out the information tree found in /tftpboot. Any operations done in the API that have not been saved with serialize() will NOT be synchronized with this command. - """ + """ self.config.deserialize(); configurator = sync.BootSync(self) return configurator.sync(dry_run) @@ -118,8 +117,8 @@ class BootAPI: """ Save the config file(s) to disk. """ - self.config.serialize() - + self.config.serialize() + def deserialize(self): """ Load the current configuration from config file(s) @@ -150,8 +149,8 @@ class Collection: for feeding to a serializer (such as YAML) """ return [x.to_datastruct() for x in self.listing.values()] - - + + def add(self,ref): """ Add an object to the collection, if it's valid. Returns True @@ -159,7 +158,7 @@ class Collection: object specified by ref deems itself invalid (and therefore won't be added to the collection). """ - if ref is None or not ref.is_valid(): + if ref is None or not ref.is_valid(): if self.api.last_error is None or self.api.last_error == "": self.api.last_error = m("bad_param") return False @@ -175,7 +174,7 @@ class Collection: """ buf = "" values = map(lambda(a): a.printable(), sorted(self.listing.values())) - if len(values) > 0: + if len(values) > 0: return "\n\n".join(values) else: return m("empty_list") @@ -194,13 +193,13 @@ class Collection: """ for a in self.listing.values(): yield a - + def __len__(self): """ Returns size of the collection """ return len(self.listing.values()) - + #-------------------------------------------- @@ -219,7 +218,7 @@ class Distros(Collection): self.api = api self.listing = {} if seed_data is not None: - for x in seed_data: + for x in seed_data: self.add(Distro(self.api,x)) def remove(self,name): @@ -236,7 +235,7 @@ class Distros(Collection): return True self.api.last_error = m("delete_nothing") return False - + #-------------------------------------------- @@ -252,7 +251,7 @@ class Profiles(Collection): self.api = api self.listing = {} if seed_data is not None: - for x in seed_data: + for x in seed_data: self.add(Profile(self.api,x)) def remove(self,name): @@ -268,7 +267,7 @@ class Profiles(Collection): return True self.api.last_error = m("delete_nothing") return False - + #-------------------------------------------- @@ -282,7 +281,7 @@ class Systems(Collection): self.api = api self.listing = {} if seed_data is not None: - for x in seed_data: + for x in seed_data: self.add(System(self.api,x)) def remove(self,name): @@ -294,7 +293,7 @@ class Systems(Collection): return True self.api.last_error = m("delete_nothing") return False - + #----------------------------------------- @@ -302,7 +301,7 @@ class Systems(Collection): An Item is a serializable thing that can appear in a Collection """ class Item: - + def set_name(self,name): """ @@ -326,7 +325,7 @@ class Item: i.e. dictionaries/arrays/scalars. """ raise exceptions.NotImplementedError - + def is_valid(self): """ The individual set_ methods will return failure if any set is @@ -334,7 +333,7 @@ class Item: the object is well formed ... i.e. have all of the important items been set, are they free of conflicts, etc. """ - return False + return False #------------------------------------------ @@ -355,7 +354,7 @@ class Distro(Item): def set_kernel(self,kernel): """ Specifies a kernel. The kernel parameter is a full path, a filename - in the configured kernel directory (set in /etc/cobbler.conf) or a + in the configured kernel directory (set in /etc/cobbler.conf) or a directory path that would contain a selectable kernel. Kernel naming conventions are checked, see docs in the utils module for find_kernel. @@ -387,9 +386,9 @@ class Distro(Item): return True def to_datastruct(self): - return { - 'name': self.name, - 'kernel': self.kernel, + return { + 'name': self.name, + 'kernel': self.kernel, 'initrd' : self.initrd, 'kernel_options' : self.kernel_options } @@ -426,14 +425,14 @@ class Profile(Item): self.kickstart = None self.kernel_options = '' self.xen_name = 'xenguest' - self.xen_file_size = 5 # GB + self.xen_file_size = 5 # GB self.xen_ram = 2048 # MB self.xen_mac = '' self.xen_paravirt = True if seed_data is not None: self.name = seed_data['name'] self.distro = seed_data['distro'] - self.kickstart = seed_data['kickstart'] + self.kickstart = seed_data['kickstart'] self.kernel_options = seed_data['kernel_options'] self.xen_name = seed_data['xen_name'] if not self.xen_name or self.xen_name == '': @@ -472,7 +471,7 @@ class Profile(Item): xen-net-install may do conflict resolution, so this is mostly a hint... To keep the shell happy, the 'str' cannot contain wildcards or slashes and may be subject to some other - untainting later. + untainting later. """ # no slashes or wildcards for bad in [ '/', '*', '?' ]: @@ -490,7 +489,7 @@ class Profile(Item): let it pick a semi-reasonable size. When in doubt, specify the size you want. """ - # num is a non-negative integer (0 means default) + # num is a non-negative integer (0 means default) try: inum = int(num) if inum != float(num): @@ -505,12 +504,12 @@ class Profile(Item): def set_xen_mac(self,mac): """ For Xen only. - Specifies the mac address (or possibly later, a range) that + Specifies the mac address (or possibly later, a range) that xen-net-install should try to set on the domU. Seeing these have a good chance of conflicting with other domU's, especially on a network, this setting is fairly experimental at this time. It's recommended that it *not* be used until we can get - decent use cases for how this might work. + decent use cases for how this might work. """ # mac needs to be in mac format AA:BB:CC:DD:EE:FF or a range # ranges currently *not* supported, so we'll fail them @@ -519,7 +518,7 @@ class Profile(Item): return True else: return False - + def set_xen_paravirt(self,truthiness): """ For Xen only. @@ -534,9 +533,9 @@ class Profile(Item): elif (truthiness == True or truthiness.lower() == 'true'): self.xen_paravirt = True else: - return False + return False except: - return False + return False return True def is_valid(self): @@ -546,12 +545,12 @@ class Profile(Item): without a kickstart is *usually* not a good idea). """ for x in (self.name, self.distro): - if x is None: + if x is None: return False return True def to_datastruct(self): - return { + return { 'name' : self.name, 'distro' : self.distro, 'kickstart' : self.kickstart, @@ -589,15 +588,15 @@ class System(Item): self.name = seed_data['name'] self.profile = seed_data['profile'] self.kernel_options = seed_data['kernel_options'] - + def set_name(self,name): """ - A name can be a resolvable hostname (it instantly resolved and replaced with the IP), + A name can be a resolvable hostname (it instantly resolved and replaced with the IP), any legal ipv4 address, or any legal mac address. ipv6 is not supported yet but _should_ be. See utils.py """ - new_name = self.api.utils.find_system_identifier(name) + new_name = self.api.utils.find_system_identifier(name) if new_name is None or new_name == False: self.api.last_error = m("bad_sys_name") return False @@ -606,7 +605,7 @@ class System(Item): def set_profile(self,profile_name): """ - Set the system to use a certain named profile. The profile + Set the system to use a certain named profile. The profile must have already been loaded into the Profiles collection. """ if self.api.get_profiles().find(profile_name): diff --git a/cobbler/check.py b/cobbler/check.py index aa5864e..bd4137a 100644 --- a/cobbler/check.py +++ b/cobbler/check.py @@ -1,5 +1,5 @@ # Classes for validating whether asystem is configured for network booting -# +# # Michael DeHaan import os @@ -13,11 +13,11 @@ class BootCheck: def __init__(self, api): self.api = api self.config = self.api.config - + def run(self): """ - Returns None if there are no errors, otherwise returns a list + Returns None if there are no errors, otherwise returns a list of things to correct prior to running application 'for real'. (The CLI usage is "cobbler check" before "cobbler sync") """ @@ -60,7 +60,7 @@ class BootCheck: Check if tftpd is installed """ if not os.path.exists(self.config.tftpd_bin): - status.append(m("no_tftpd")) + status.append(m("no_tftpd")) def check_tftpd_dir(self,status): """ @@ -68,7 +68,7 @@ class BootCheck: """ if not os.path.exists(self.config.tftpboot): status.append(m("no_dir") % self.config.tftpboot) - + def check_tftpd_conf(self,status): """ @@ -88,10 +88,10 @@ class BootCheck: if line.find("-s %s" % self.config.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.config.tftpboot, self.config.tftpd_conf)) else: status.append(m("no_exist") % self.config.tftpd_conf) - + def check_dhcpd_conf(self,status): """ @@ -103,10 +103,10 @@ class BootCheck: match_file = False f = open(self.config.dhcpd_conf) for line in f.readlines(): - if line.find("next-server") != -1: + if line.find("next-server") != -1: match_next = True if line.find("filename") != -1: - match_file = True + match_file = True if not match_next: status.append(m("no_line") % (self.config.dhcpd_conf, 'next-server ip-address')) if not match_file: diff --git a/cobbler/cobbler b/cobbler/cobbler new file mode 100755 index 0000000..f62e10f --- /dev/null +++ b/cobbler/cobbler @@ -0,0 +1,6 @@ +#!/usr/bin/python + +import sys +import cobbler +sys.exit(cobbler.main()) + diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 30baaff..47c8c5a 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -21,7 +21,7 @@ class BootCLI: self.args = args self.api = api.BootAPI() self.commands = {} - self.commands['distro'] = { + self.commands['distro'] = { 'add' : self.distro_edit, 'edit' : self.distro_edit, 'delete' : self.distro_remove, @@ -46,8 +46,8 @@ class BootCLI: 'check' : self.check, 'distros' : self.distro, 'distro' : self.distro, - 'profiles' : self.profile, - 'profile' : self.profile, + 'profiles' : self.profile, + 'profile' : self.profile, 'systems' : self.system, 'system' : self.system, 'sync' : self.sync, @@ -100,7 +100,7 @@ class BootCLI: Delete a system: 'cobbler system remove --name=foo' """ commands = { - '--name' : lambda(a): self.api.get_systems().remove(a) + '--name' : lambda(a): self.api.get_systems().remove(a) } on_ok = lambda: True return self.apply_args(args,commands,on_ok,True) @@ -135,8 +135,8 @@ class BootCLI: sys = self.api.new_system() commands = { '--name' : lambda(a) : sys.set_name(a), - '--profile' : lambda(a) : sys.set_profile(a), - '--profiles' : lambda(a) : sys.set_profile(a), # alias + '--profile' : lambda(a) : sys.set_profile(a), + '--profiles' : lambda(a) : sys.set_profile(a), # alias '--kopts' : lambda(a) : sys.set_kernel_options(a) } on_ok = lambda: self.api.get_systems().add(sys) @@ -157,13 +157,13 @@ class BootCLI: '--xen-file-size' : lambda(a) : profile.set_xen_file_size(a), '--xen-ram' : lambda(a) : profile.set_xen_ram(a) # the following options are most likely not useful for profiles (yet) - # primarily due to not being implemented in koan. + # primarily due to not being implemented in koan. # '--xen-mac' : lambda(a) : profile.set_xen_mac(a), # '--xen-paravirt' : lambda(a) : profile.set_xen_paravirt(a), } on_ok = lambda: self.api.get_profiles().add(profile) return self.apply_args(args,commands,on_ok,True) - + def distro_edit(self,args): """ @@ -226,7 +226,7 @@ class BootCLI: return False if args[0] in commands: # if the subargument is in the dispatch table, run - # the selected command routine with the rest of the + # the selected command routine with the rest of the # arguments rc = commands[args[0]](args[1:]) if not rc: @@ -241,13 +241,13 @@ class BootCLI: """ Sync the config file with the system config: 'cobbler sync [--dryrun]' """ - status = None + status = None if args is not None and "--dryrun" in args: status = self.api.sync(dry_run=True) else: status = self.api.sync(dry_run=False) return status - + def check(self,args): """ @@ -261,16 +261,16 @@ class BootCLI: return True else: print m("need_to_fix") - for i,x in enumerate(status): + for i,x in enumerate(status): print "#%d: %s" % (i,x) return False - + def distro(self,args): """ Handles any of the 'cobbler distro' subcommands """ - return self.curry_args(args, self.commands['distro']) + return self.curry_args(args, self.commands['distro']) def profile(self,args): @@ -292,9 +292,9 @@ def main(): """ if os.getuid() != 0: # while it's true that we don't technically need root, we do need - # permissions on a relatively long list of files that ordinarily + # permissions on a relatively long list of files that ordinarily # only root has access to, and we don't know specifically what - # files are where (other distributions in play, etc). It's + # files are where (other distributions in play, etc). It's # fairly safe to assume root is required. This might be patched # later. print m("need_root") diff --git a/cobbler/config.py b/cobbler/config.py index 8c8991a..26192a7 100644 --- a/cobbler/config.py +++ b/cobbler/config.py @@ -3,17 +3,19 @@ # # Michael DeHaan -import api +import api import util from msg import * +import yaml # the yaml parser from RHN's spec-tree (Howell/Evans) import os -import yaml # python yaml 3000 from pyyaml.org, soon to be in extras import traceback global_settings_file = "/etc/cobbler.conf" global_state_file = "/var/lib/cobbler/cobbler.conf" + + class BootConfig: def __init__(self,api): @@ -52,7 +54,7 @@ class BootConfig: self.tftpboot = "/tftpboot" self.dhcpd_conf = "/etc/dhcpd.conf" self.tftpd_conf = "/etc/xinetd.d/tftp" - self.pxelinux = "/usr/lib/syslinux/pxelinux.0" + 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" @@ -91,7 +93,7 @@ class BootConfig: 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) @@ -109,21 +111,20 @@ class BootConfig: 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 = {} + 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() - #print "DEBUG: %s" % (world) - return world + return world def from_hash(self,hash,is_etc): @@ -150,7 +151,7 @@ class BootConfig: settings = None state = None - + # ------ # dump global config (pathing, urls, etc)... try: @@ -160,11 +161,13 @@ class BootConfig: return False data = self.to_hash(True) settings.write(yaml.dump(data)) - + # ------ # dump internal state (distros, profiles, systems...) if not os.path.isdir(os.path.dirname(self.state_file)): - os.makedirs(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: @@ -186,28 +189,31 @@ class BootConfig: # ----- # load global config (pathing, urls, etc)... try: - settings = yaml.load(open(self.settings_file,"r").read()) + settings = yaml.load(open(self.settings_file,"r").read()).next() if settings is not None: - return self.from_hash(settings,True) + self.from_hash(settings,True) else: - print "WARNING: no %s data?" % self.settings_file + self.last_error = m("parse_error") + return False except: + traceback.print_exc() self.api.last_error = m("parse_error") return False # ----- # load internal state(distros, systems, profiles...) try: - state = yaml.load(open(self.state_file,"r").read()) + state = yaml.load(open(self.state_file,"r").read()).next() if state is not None: - return self.from_hash(state,False) + self.from_hash(state,False) else: - print "WARNING: no %s data?" % self.state_file + self.last_error = m("parse_error") + return False except: traceback.print_exc() self.api.last_error = m("parse_error2") return False - + # all good return True diff --git a/cobbler/msg.py b/cobbler/msg.py index e974bc6..d770fd2 100644 --- a/cobbler/msg.py +++ b/cobbler/msg.py @@ -22,13 +22,13 @@ msg_table = { "need_to_fix" : "the following potential problems were detected:", "need_root" : "cobber must be run as root", "no_dhcpd" : "can't find dhcpd, try 'yum install dhcpd'", - "no_pxelinux" : "can't find pxelinux, try 'yum install pxelinux'", + "no_pxelinux" : "can't find pxelinux, try 'yum install pxelinux'", "no_tftpd" : "can't find tftpd, try 'yum install tftpd'", "no_dir" : "can't find %s, need to create it", "chg_attrib" : "need to change '%s' to '%s' in '%s'", "no_exist" : "%s does not exist", "no_line" : "file '%s' should have a line '%s' somewhere", - "no_dir2" : "can't find %s for %s in cobber.conf", + "no_dir2" : "can't find %s for %s in cobber.conf", "no_cfg" : "could not find cobber.conf, recreating", "bad_param" : "at least one parameter is missing for this function", "empty_list" : "(Empty)", @@ -46,9 +46,9 @@ msg_table = { "no_kernel" : "the kernel needs to be a directory containing a kernel, or a full path. Kernels must be named just 'vmlinuz' or in the form 'vmlinuz-AA.BB.CC-something'", "no_initrd" : "the initrd needs to be a directory containing an initrd, or a full path. Initrds must be named just 'initrd.img' or in the form 'initrd-AA.BB.CC-something.img", "check_ok" : """ -No setup problems found. +No setup problems found. -Manual editing of /etc/dhcpd.conf and /etc/cobber.conf is suggested to tailor them to your specific configuration. Your dhcpd.conf has some PXE related information in it, but it's imposible to tell automatically that it's totally correct in a general sense. We'll leave this up to you. +Manual editing of /etc/dhcpd.conf and /etc/cobber.conf is suggested to tailor them to your specific configuration. Your dhcpd.conf has some PXE related information in it, but it's imposible to tell automatically that it's totally correct in a general sense. We'll leave this up to you. Good luck. """, diff --git a/cobbler/sync.py b/cobbler/sync.py index a31e262..ae20872 100644 --- a/cobbler/sync.py +++ b/cobbler/sync.py @@ -10,9 +10,9 @@ import sys import traceback import re import shutil +import yaml # from RHN's spec-tree (Howell/Evans) import IPy -import yaml from msg import * """ @@ -28,7 +28,7 @@ class BootSync: def sync(self,dry_run=False,verbose=True): """ - Syncs the current configuration file with the config tree. + Syncs the current configuration file with the config tree. Using the Check().run_ functions previously is recommended """ self.dry_run = dry_run @@ -53,7 +53,7 @@ class BootSync: def configure_httpd(self): """ - Create a config file to Apache that will allow access to the + Create a config file to Apache that will allow access to the cobbler infrastructure available over TFTP over HTTP also. """ if not os.path.exists("/etc/httpd/conf.d"): @@ -82,7 +82,7 @@ class BootSync: """ for x in ["pxelinux.cfg","images","systems","distros","profiles","kickstarts"]: path = os.path.join(self.api.config.tftpboot,x) - self.rmtree(path, True) + self.rmtree(path, True) self.mkdir(path) def copy_distros(self): @@ -140,7 +140,7 @@ class BootSync: def build_trees(self): """ Now that kernels and initrds are copied and kickstarts are all valid, - build the pxelinux.cfg tree, which contains a directory for each + build the pxelinux.cfg tree, which contains a directory for each configured IP or MAC address. Also build a parallel 'xeninfo' tree for xen-net-install info. """ @@ -182,7 +182,7 @@ class BootSync: self.api.last_error = m("orphan_profile2") raise "error" distro = distros.find(profile.distro) - if distro is None: + if distro is None: self.api.last_error = m("orphan_system2") raise "error" f1 = self.get_pxelinux_filename(system.name) @@ -208,7 +208,7 @@ class BootSync: else: self.api.last_error = m("err_resolv") % name raise "error" - + def write_pxelinux_file(self,filename,system,profile,distro): """ @@ -228,9 +228,9 @@ class BootSync: self.tee(fd,"label linux\n") self.tee(fd," kernel %s\n" % kernel_path) kopts = self.blend_kernel_options(( - self.api.config.kernel_options, - profile.kernel_options, - distro.kernel_options, + self.api.config.kernel_options, + profile.kernel_options, + distro.kernel_options, system.kernel_options )) nextline = " append %s initrd=%s" % (kopts,initrd_path) @@ -246,7 +246,7 @@ class BootSync: def write_distro_file(self,filename,distro): - """ + """ Create distro information for xen-net-install """ fd = self.open_file(filename,"w+") @@ -268,12 +268,12 @@ class BootSync: profile.kickstart = "http://%s/cobbler/kickstarts/%s/ks.cfg" % (self.api.config.server, profile.name) self.tee(fd,yaml.dump(profile.to_datastruct())) self.close_file(fd) - + def write_system_file(self,filename,system): """ Create system information for xen-net-install - """ + """ fd = self.open_file(filename,"w+") self.tee(fd,yaml.dump(system.to_datastruct())) self.close_file(fd) @@ -285,7 +285,7 @@ class BootSync: self.sync_log(text) if not self.dry_run: fd.write(text) - + def open_file(self,filename,mode): """ For dry_run support: open a file if not in dry_run mode. @@ -293,7 +293,7 @@ class BootSync: if self.dry_run: return None return open(filename,mode) - + def close_file(self,fd): """ For dry_run support: close a file if not in dry_run mode. @@ -347,15 +347,15 @@ class BootSync: print "dry_run | %s" % message else: print message - + def blend_kernel_options(self, list_of_opts): """ Given a list of kernel options, take the values used by the first argument in the list unless overridden by those in the - second (or further on), according to --key=value formats. + second (or further on), according to --key=value formats. - This is used such that we can have default kernel options - in /etc and then distro, profile, and system options with various + This is used such that we can have default kernel options + in /etc and then distro, profile, and system options with various levels of configurability. """ internal = {} @@ -377,12 +377,12 @@ class BootSync: data = internal[key] if key == "ks" or key == "initrd" or key == "append": # the user REALLY doesn't want to do this... - continue + continue if data == "": results.append(key) - else: + else: results.append("%s=%s" % (key,internal[key])) # end result is a new fragment of a kernel options string return " ".join(results) - + diff --git a/cobbler/util.py b/cobbler/util.py index e8cdf3f..cdf7bbe 100644 --- a/cobbler/util.py +++ b/cobbler/util.py @@ -1,5 +1,5 @@ # Misc heavy lifting functions for cobbler -# +# # Michael DeHaan import config @@ -59,7 +59,7 @@ class BootUtil: try: return socket.gethostbyname(strdata) except: - return None + return None def find_matching_files(self,directory,regex): @@ -68,17 +68,17 @@ class BootUtil: Can't use glob directly as glob doesn't take regexen. """ files = glob.glob(os.path.join(directory,"*")) - results = [] + results = [] for f in files: if regex.match(os.path.basename(f)): results.append(f) return results - + def find_highest_files(self,directory,unversioned,regex): """ Find the highest numbered file (kernel or initrd numbering scheme) - in a given directory that matches a given pattern. Used for + in a given directory that matches a given pattern. Used for auto-booting the latest kernel in a directory. """ files = self.find_matching_files(directory, regex) @@ -87,12 +87,12 @@ class BootUtil: av = get_numbers.search(os.path.basename(a)).groups() bv = get_numbers.search(os.path.basename(b)).groups() if av[0]bv[0]: return 1 + elif av[0]>bv[0]: return 1 elif av[1]bv[1]: return 1 elif av[2]bv[2]: return 1 - return 0 + return 0 if len(files) > 0: return sorted(files, sort)[-1] else: @@ -102,13 +102,13 @@ class BootUtil: if os.path.exists(last_chance): return last_chance return None - + def find_kernel(self,path): """ Given a directory or a filename, find if the path can be made to resolve into a kernel, and return that full path if possible. - """ + """ if os.path.isfile(path): filename = os.path.basename(path) if self.re_kernel.match(filename): @@ -122,7 +122,7 @@ class BootUtil: def find_initrd(self,path): """ - Given a directory or a filename, see if the path can be made + Given a directory or a filename, see if the path can be made to resolve into an intird, return that full path if possible. """ # FUTURE: add another function to see if kernel and initrd have matched numbers (and throw a warning?) @@ -133,9 +133,9 @@ class BootUtil: if filename == "initrd.img" or filename == "initrd": return path elif os.path.isdir(path): - return self.find_highest_files(path,"initrd.img",self.re_initrd) + return self.find_highest_files(path,"initrd.img",self.re_initrd) return None - + def find_kickstart(self,url): """ diff --git a/examples/example_ks b/examples/example_ks index 3da6099..438be2a 100644 --- a/examples/example_ks +++ b/examples/example_ks @@ -1,10 +1,10 @@ #platform=x86, AMD64, or Intel EM64T # System authorization information -auth --useshadow --enablemd5 +auth --useshadow --enablemd5 # System bootloader configuration bootloader --location=mbr # Partition clearing information -clearpart --linux --initlabel +clearpart --linux --initlabel # Use graphical install text # Firewall configuration diff --git a/tests/tests.py b/tests/tests.py index 1a54149..440c588 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -14,11 +14,6 @@ sys.path.append('./cobbler') import api import config -# rewrite default configuration locations so anyone -# with a real config won't get hurt -config.global_settings_file = "./tests/etc/cobbler.conf" -config.global_state_file = "./tests/var/lib/cobbler/cobbler.conf" - FAKE_INITRD="/tmp/initrd-2.6.15-1.2054_FAKE.img" FAKE_INITRD2="/tmp/initrd-2.5.16-2.2055_FAKE.img" FAKE_INITRD3="/tmp/initrd-1.8.18-3.9999_FAKE.img" @@ -80,10 +75,10 @@ class Utilities(BootTest): self.assertFalse(self.api.utils.find_kernel("/etc/fstab")) self.assertFalse(self.api.utils.find_initrd("filedoesnotexist")) self.assertTrue(self.api.utils.find_initrd("/tmp") == FAKE_INITRD) - + def test_kickstart_scan(self): # we don't check to see if kickstart files look like anything - # so this will pass + # so this will pass self.assertTrue(self.api.utils.find_kickstart(FAKE_INITRD) is None) self.assertTrue(self.api.utils.find_kickstart("filedoesnotexist") is None) self.assertTrue(self.api.utils.find_kickstart("/tmp") == None) @@ -100,7 +95,7 @@ class Utilities(BootTest): self.assertTrue(self.api.utils.is_ip("127.0.0.1")) self.assertTrue(self.api.utils.is_ip("192.168.1.1")) self.assertFalse(self.api.utils.is_ip("00:C0:B7:7E:55:50")) - self.assertFalse(self.api.utils.is_ip(self.hostname)) + self.assertFalse(self.api.utils.is_ip(self.hostname)) class Additions(BootTest): @@ -122,7 +117,7 @@ class Additions(BootTest): self.assertFalse(distro.set_initrd("filedoesntexist")) self.assertFalse(self.api.get_distros().add(distro)) self.assertFalse(self.api.get_distros().find("testdistro3")) - + def test_invalid_profile_non_referenced_distro(self): profile = self.api.new_profile() self.assertTrue(profile.set_name("testprofile11")) @@ -161,7 +156,7 @@ class Additions(BootTest): self.assertFalse(profile.set_xen_file_size("54321.23")) # macs must be properly formatted self.assertTrue(profile.set_xen_mac("AA:BB:CC:DD:EE:FF")) - self.assertFalse(profile.set_xen_mac("AA-BB-CC-DD-EE-FF")) + self.assertFalse(profile.set_xen_mac("AA-BB-CC-DD-EE-FF")) # paravirt must be 'true' or 'false' self.assertFalse(profile.set_xen_mac("cowbell")) self.assertTrue(profile.set_xen_paravirt('true')) @@ -203,7 +198,7 @@ class Additions(BootTest): self.assertFalse(self.api.get_systems().add(system)) class Deletions(BootTest): - + def test_invalid_delete_profile_doesnt_exist(self): self.assertFalse(self.api.get_profiles().remove("doesnotexist")) @@ -220,7 +215,7 @@ class Deletions(BootTest): def test_invalid_delete_distro_would_orphan_profile(self): self.make_basic_config() self.assertFalse(self.api.get_distros().remove("testdistro0")) - + def test_working_deletes(self): self.api.clear() self.make_basic_config() @@ -232,23 +227,8 @@ class Deletions(BootTest): self.assertFalse(self.api.get_profiles().find("testprofile0")) self.assertFalse(self.api.get_distros().find("testdistro0")) -class TestSerialization(BootTest): - - def test_serialization(self): - self.make_basic_config() - self.api.serialize() - self.api.clear() - 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.api.deserialize() - self.assertTrue(self.api.get_systems().find(self.hostname)) - self.assertTrue(self.api.get_profiles().find("testprofile0")) - self.assertTrue(self.api.get_distros().find("testdistro0")) - - class TestCheck(BootTest): - + def test_check(self): # we can't know if it's supposed to fail in advance # (ain't that the halting problem), but it shouldn't ever @@ -256,7 +236,7 @@ class TestCheck(BootTest): self.api.check() class TestSync(BootTest): - + def test_dry_run(self): # dry_run just *shows* what is done, it doesn't apply the config # the test here is mainly for coverage, we do not test @@ -271,7 +251,7 @@ class TestSync(BootTest): pass class TestListings(BootTest): - + def test_listings(self): # check to see if the collection listings output something. # this is a minimal check, mainly for coverage, not validity -- cgit