[78] | 1 | #!@PYTHON@ |
---|
[2] | 2 | # |
---|
[104] | 3 | # set ts=4, sw=4 |
---|
| 4 | # |
---|
[2] | 5 | # Author: Bas van der Vlies <basv@sara.nl> |
---|
| 6 | # Date : 16 February 2002 |
---|
| 7 | # |
---|
| 8 | # Tester: Walter de Jong <walter@sara.nl> |
---|
| 9 | # |
---|
[29] | 10 | # SVN info |
---|
| 11 | # $Id: pxeconfig.in 137 2009-04-15 12:12:38Z bas $ |
---|
[2] | 12 | # |
---|
[14] | 13 | # Copyright (C) 2002 |
---|
[6] | 14 | # |
---|
[14] | 15 | # This file is part of the pxeconfig utils |
---|
[6] | 16 | # |
---|
[14] | 17 | # This program is free software; you can redistribute it and/or modify it |
---|
| 18 | # under the terms of the GNU General Public License as published by the |
---|
| 19 | # Free Software Foundation; either version 2, or (at your option) any |
---|
| 20 | # later version. |
---|
[6] | 21 | # |
---|
[14] | 22 | # This program is distributed in the hope that it will be useful, |
---|
| 23 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
| 24 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
| 25 | # GNU General Public License for more details. |
---|
[6] | 26 | # |
---|
[14] | 27 | # You should have received a copy of the GNU General Public License |
---|
| 28 | # along with this program; if not, write to the Free Software |
---|
| 29 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA |
---|
| 30 | # |
---|
[2] | 31 | """ |
---|
[133] | 32 | Usage: pxeconfig [-f,--filename <name>] <hosts> |
---|
[49] | 33 | |
---|
[133] | 34 | Specifying hostnames: |
---|
| 35 | To specify a range use the [] to indicate a range, |
---|
| 36 | a couple of examples herebelow. |
---|
[10] | 37 | |
---|
[133] | 38 | The first five nodes of rack 16 |
---|
| 39 | - gb-r16n[1-5] |
---|
[123] | 40 | |
---|
[133] | 41 | The first five nodes and node 12 and 18 of rack 16 to 20 |
---|
| 42 | - gb-r[16-20]n[1-5,12,18] |
---|
[123] | 43 | |
---|
[137] | 44 | The first five nodes de in rack 16 with padding enabled |
---|
[133] | 45 | - gb-r[16]n[01-5] |
---|
[118] | 46 | |
---|
[133] | 47 | The ranges ([]) are not only limited to numbers, letters can also be used. |
---|
[123] | 48 | |
---|
[19] | 49 | With this program you can configure which PXE configuration file a node |
---|
[133] | 50 | will use when it boots. |
---|
[19] | 51 | |
---|
[126] | 52 | See following link for usage and examples: |
---|
| 53 | - https://subtrac.sara.nl/oss/pxeconfig/wiki/PxeUsage |
---|
[2] | 54 | """ |
---|
| 55 | |
---|
| 56 | import string |
---|
| 57 | import sys |
---|
| 58 | import os |
---|
| 59 | import glob |
---|
[3] | 60 | import getopt |
---|
[47] | 61 | import socket |
---|
[74] | 62 | import ConfigParser |
---|
[123] | 63 | import re |
---|
[2] | 64 | |
---|
[132] | 65 | # import from the sara_python_modules |
---|
| 66 | from sara import AdvancedParser |
---|
| 67 | |
---|
[2] | 68 | # Constants |
---|
| 69 | # |
---|
[133] | 70 | PXE_CONF_DIR = '/tftpboot/pxelinux.cfg' |
---|
| 71 | NETWORK = 'network' |
---|
| 72 | START = 'start' |
---|
| 73 | END = 'end' |
---|
| 74 | VERSION = '3.0.0' |
---|
[2] | 75 | |
---|
[119] | 76 | class PxeConfig(Exception): |
---|
| 77 | def __init__(self, msg=''): |
---|
| 78 | self.msg = msg |
---|
| 79 | Exception.__init__(self, msg) |
---|
| 80 | |
---|
| 81 | def __repr__(self): |
---|
| 82 | return self.msg |
---|
| 83 | |
---|
[75] | 84 | def ReadConfig(file): |
---|
[74] | 85 | """ |
---|
| 86 | Parse the config file |
---|
| 87 | """ |
---|
| 88 | if not os.path.isfile(file): |
---|
[136] | 89 | error = 'File %s does not exist' %file |
---|
| 90 | raise PxeConfig, error |
---|
[74] | 91 | |
---|
[75] | 92 | config = ConfigParser.RawConfigParser() |
---|
[74] | 93 | try: |
---|
| 94 | config.read(file) |
---|
| 95 | except ConfigParser.MissingSectionHeaderError,detail: |
---|
[136] | 96 | raise PxeConfig, detail |
---|
[74] | 97 | |
---|
[75] | 98 | # Not yet uses |
---|
[74] | 99 | # |
---|
[75] | 100 | #projects = {} |
---|
| 101 | #for section in config.sections(): |
---|
| 102 | # projects[section] = {} |
---|
| 103 | # for option in config.options(section): |
---|
| 104 | # projects[section][option] = config.get(section, option) |
---|
[74] | 105 | |
---|
[75] | 106 | stanza = config.defaults() |
---|
| 107 | return stanza |
---|
[74] | 108 | |
---|
[19] | 109 | def select_pxe_configfile(): |
---|
[133] | 110 | """ |
---|
| 111 | Let user choose which pxeconfig file to use. |
---|
| 112 | """ |
---|
| 113 | |
---|
| 114 | os.chdir(PXE_CONF_DIR) |
---|
| 115 | |
---|
| 116 | # Try to determine to which file the default symlink points, and |
---|
| 117 | # if it exists, remove it from the list. |
---|
| 118 | # |
---|
| 119 | try: |
---|
| 120 | default_file = os.readlink('default') |
---|
| 121 | except OSError: |
---|
| 122 | default_file = None |
---|
| 123 | pass |
---|
[2] | 124 | |
---|
[133] | 125 | files = glob.glob('default.*') |
---|
| 126 | if not files: |
---|
[136] | 127 | error = 'There are no pxe config files starting with: default.' |
---|
| 128 | raise PxeConfig, error |
---|
[2] | 129 | |
---|
[133] | 130 | if default_file: |
---|
| 131 | files.remove(default_file) |
---|
[2] | 132 | |
---|
[133] | 133 | # sort the files |
---|
| 134 | # |
---|
| 135 | files.sort() |
---|
[2] | 136 | |
---|
[133] | 137 | print 'Which pxe config file must we use: ?' |
---|
| 138 | i = 1 |
---|
| 139 | for file in files: |
---|
| 140 | print "%d : %s" %(i,file) |
---|
| 141 | i = i + 1 |
---|
[2] | 142 | |
---|
[133] | 143 | while 1: |
---|
| 144 | index = raw_input('Select a number: ') |
---|
| 145 | try: |
---|
| 146 | index = int(index) |
---|
| 147 | except ValueError: |
---|
| 148 | index = len(files) + 1 |
---|
[2] | 149 | |
---|
[133] | 150 | # Is the user smart enough to select |
---|
| 151 | # the right value?? |
---|
| 152 | # |
---|
| 153 | if 0 < index <= len(files): |
---|
| 154 | break |
---|
[2] | 155 | |
---|
[133] | 156 | return files[index-1] |
---|
[2] | 157 | |
---|
[135] | 158 | def manage_links(haddr, options): |
---|
[126] | 159 | """ |
---|
| 160 | Create the links in the PXE_CONF_DIR, |
---|
| 161 | list : A list containing: network hex address, pxe config file, |
---|
[2] | 162 | start number, end number |
---|
[126] | 163 | """ |
---|
[133] | 164 | if options.VERBOSE: |
---|
[135] | 165 | print 'manage_links(%s)' %(haddr) |
---|
[126] | 166 | |
---|
| 167 | os.chdir(PXE_CONF_DIR) |
---|
[133] | 168 | pxe_filename = options.filename |
---|
[126] | 169 | |
---|
[135] | 170 | if options.REMOVE: |
---|
[136] | 171 | if options.DEBUG or options.DRY_RUN or options.VERBOSE: |
---|
[135] | 172 | print 'removing %s/%s' %(PXE_CONF_DIR, haddr) |
---|
[125] | 173 | |
---|
[135] | 174 | if os.path.exists(haddr) and not options.DRY_RUN: |
---|
| 175 | os.unlink(haddr) |
---|
[122] | 176 | |
---|
[135] | 177 | else: |
---|
[136] | 178 | if options.DEBUG or options.DRY_RUN or options.VERBOSE: |
---|
[135] | 179 | print 'linking %s to %s' %(haddr, pxe_filename) |
---|
[15] | 180 | |
---|
[135] | 181 | if not options.DRY_RUN: |
---|
| 182 | if os.path.exists(haddr): |
---|
| 183 | os.unlink(haddr) |
---|
| 184 | os.symlink(pxe_filename, haddr) |
---|
[15] | 185 | |
---|
[133] | 186 | def net_2_hex(net, options): |
---|
[119] | 187 | """ |
---|
| 188 | This function checks if the give network is a Class C-network and will |
---|
| 189 | convert the network address to a hex address if true. |
---|
| 190 | """ |
---|
[136] | 191 | if options.DEBUG: |
---|
[133] | 192 | str = 'net_2_hex : %s' %(net) |
---|
| 193 | print str |
---|
[119] | 194 | |
---|
| 195 | d = string.split(net, '.') |
---|
| 196 | if len(d) != 3: |
---|
| 197 | error = '%s is not a valid C-class network address' %(net) |
---|
| 198 | raise PxeConfig, error |
---|
[2] | 199 | |
---|
[119] | 200 | # Check if we have valid network values |
---|
| 201 | r = '' |
---|
| 202 | for i in d: |
---|
[133] | 203 | r = '%s%02X' %(r, int(i)) |
---|
[2] | 204 | |
---|
[133] | 205 | if options.DEBUG: |
---|
| 206 | print 'C-network converted to hex: ', r |
---|
[19] | 207 | |
---|
[119] | 208 | return r |
---|
[19] | 209 | |
---|
[136] | 210 | def hosts_2_hex(hosts, options): |
---|
| 211 | """ |
---|
| 212 | Convert hostname(s) to a net address that can be handled by manage_links function |
---|
| 213 | """ |
---|
| 214 | if options.DEBUG: |
---|
| 215 | str = 'host_2_hex: %s' %hosts |
---|
| 216 | print str |
---|
| 217 | |
---|
| 218 | for host in hosts: |
---|
| 219 | try: |
---|
| 220 | addr = socket.gethostbyname(host) |
---|
| 221 | except socket.error,detail: |
---|
| 222 | error = '%s not an valid hostname: %s' %(host,detail) |
---|
| 223 | raise PxeConfig, error |
---|
| 224 | |
---|
| 225 | net = string.splitfields(addr, '.') |
---|
| 226 | cnet = string.joinfields(net[0:3], '.') |
---|
| 227 | |
---|
| 228 | haddr = '%s%02X' %(net_2_hex(cnet, options), int(net[3])) |
---|
| 229 | |
---|
| 230 | manage_links(haddr, options) |
---|
| 231 | |
---|
| 232 | |
---|
[132] | 233 | def add_options(p): |
---|
| 234 | """ |
---|
| 235 | add the default options |
---|
| 236 | """ |
---|
| 237 | p.set_defaults( |
---|
| 238 | DEBUG = False, |
---|
| 239 | VERBOSE = False, |
---|
| 240 | DRY_RUN = False, |
---|
| 241 | REMOVE = False, |
---|
| 242 | VERSION = False, |
---|
| 243 | ) |
---|
| 244 | |
---|
[136] | 245 | p.add_option('-d', '--debug', |
---|
[133] | 246 | action = 'store_true', |
---|
[136] | 247 | dest = 'DEBUG', |
---|
| 248 | help = 'Toggle debug mode (default : False)' |
---|
[133] | 249 | ) |
---|
| 250 | |
---|
[132] | 251 | p.add_option('-f', '--filename', |
---|
| 252 | action = 'store', |
---|
| 253 | dest = 'filename', |
---|
| 254 | help = 'Specifies which PXE filename must be use' |
---|
| 255 | ) |
---|
| 256 | |
---|
[136] | 257 | p.add_option('-n', '--dry-run', |
---|
| 258 | action = 'store_true', |
---|
| 259 | dest = 'DRY_RUN', |
---|
| 260 | help = 'Do not execute any command' |
---|
| 261 | ) |
---|
| 262 | |
---|
[132] | 263 | p.add_option('-r', '--remove', |
---|
| 264 | action = 'store_true', |
---|
| 265 | dest = 'REMOVE', |
---|
| 266 | help = 'Removes the PXE filename for a host(s)' |
---|
| 267 | ) |
---|
| 268 | |
---|
[133] | 269 | p.add_option('-v', '--verbose', |
---|
| 270 | action = 'store_true', |
---|
| 271 | dest = 'VERBOSE', |
---|
[136] | 272 | help = 'Make the program more verbose (default: False)' |
---|
[133] | 273 | ) |
---|
| 274 | |
---|
[132] | 275 | p.add_option('-V', '--version', |
---|
| 276 | action = 'store_true', |
---|
| 277 | dest = 'VERSION', |
---|
[133] | 278 | help = 'Print the program version number and exit' |
---|
[132] | 279 | ) |
---|
| 280 | |
---|
[136] | 281 | def parser(argv, settings): |
---|
[132] | 282 | """ |
---|
[133] | 283 | Make use of sara advance parser module. It can handle regex in hostnames |
---|
[132] | 284 | """ |
---|
[133] | 285 | parser = AdvancedParser.AdvancedParser(usage=__doc__) |
---|
[132] | 286 | add_options(parser) |
---|
| 287 | |
---|
[133] | 288 | options, args = parser.parse_args() |
---|
[135] | 289 | |
---|
| 290 | if not args: |
---|
| 291 | print __doc__ |
---|
| 292 | sys.exit(0) |
---|
[123] | 293 | |
---|
[133] | 294 | if options.VERSION: |
---|
| 295 | print VERSION |
---|
| 296 | sys.exit(0) |
---|
[3] | 297 | |
---|
[136] | 298 | # debug can be specified by the command line or options file |
---|
| 299 | # |
---|
| 300 | if not options.DEBUG: |
---|
| 301 | try: |
---|
| 302 | if settings['debug']: |
---|
| 303 | options.DEBUG = int(settings['debug']) |
---|
| 304 | except KeyError: |
---|
| 305 | pass |
---|
| 306 | |
---|
[133] | 307 | if options.filename: |
---|
| 308 | if not os.path.isfile(os.path.join(PXE_CONF_DIR, options.filename)): |
---|
| 309 | error = '%s: Filename does not exists' %(options.filename) |
---|
[136] | 310 | raise PxeConfig, error |
---|
[133] | 311 | else: |
---|
| 312 | options.filename = select_pxe_configfile() |
---|
[105] | 313 | |
---|
[133] | 314 | if options.DEBUG: |
---|
| 315 | print args, options |
---|
[123] | 316 | |
---|
[133] | 317 | hosts_2_hex(args, options) |
---|
[126] | 318 | |
---|
[125] | 319 | |
---|
[2] | 320 | def main(): |
---|
[104] | 321 | # A dictionary holding the boot info |
---|
| 322 | # |
---|
[117] | 323 | global PXE_CONF_DIR |
---|
[104] | 324 | |
---|
| 325 | configfile = '@pxeconfig_conf@' |
---|
| 326 | settings = ReadConfig(configfile) |
---|
| 327 | |
---|
| 328 | try: |
---|
| 329 | PXE_CONF_DIR = settings['pxe_config_dir'] |
---|
[117] | 330 | |
---|
[104] | 331 | except KeyError: |
---|
| 332 | pass |
---|
[105] | 333 | |
---|
[118] | 334 | PXE_CONF_DIR = os.path.realpath(PXE_CONF_DIR) |
---|
[117] | 335 | if not os.path.isdir(PXE_CONF_DIR): |
---|
[120] | 336 | error = 'pxeconfig directory: %s does not exists' %(PXE_CONF_DIR) |
---|
| 337 | raise PxeConfig, error |
---|
[117] | 338 | |
---|
[136] | 339 | parser(sys.argv, settings) |
---|
| 340 | |
---|
[120] | 341 | |
---|
| 342 | if __name__ == '__main__': |
---|
[119] | 343 | try: |
---|
[133] | 344 | main() |
---|
[119] | 345 | except PxeConfig, detail: |
---|
| 346 | print detail |
---|
| 347 | sys.exit(1) |
---|