X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=PLC%2FMethods%2FGetBootMedium.py;h=53f68a1e5f693c6736d5f0d19b7126ac05ed7598;hb=582e7e8ea3bb3d2933ab4dd7740ba5894a0daaed;hp=9da8a2b6709b87bd2cfe709aa06a0644ee324673;hpb=b11b69bead23f00eeeb6701ee1eb19ebb20307f2;p=plcapi.git diff --git a/PLC/Methods/GetBootMedium.py b/PLC/Methods/GetBootMedium.py index 9da8a2b..53f68a1 100644 --- a/PLC/Methods/GetBootMedium.py +++ b/PLC/Methods/GetBootMedium.py @@ -1,4 +1,5 @@ # $Id$ +# $URL$ import random import base64 import os @@ -13,22 +14,28 @@ from PLC.Auth import Auth from PLC.Nodes import Node, Nodes from PLC.Interfaces import Interface, Interfaces from PLC.InterfaceTags import InterfaceTag, InterfaceTags +from PLC.NodeTags import NodeTag, NodeTags + +from PLC.Accessors.Accessors_standard import * # import node accessors # could not define this in the class.. # create a dict with the allowed actions for each type of node +# reservable nodes being more recent, we do not support the floppy stuff anymore allowed_actions = { - 'regular' : [ 'node-preview', - 'node-floppy', - 'node-iso', - 'node-usb', - 'generic-iso', - 'generic-usb', - ], - 'dummynet' : [ 'node-preview', - 'dummynet-iso', - 'dummynet-usb', - ], - } + 'regular' : + [ 'node-preview', + 'node-floppy', + 'node-iso', + 'node-usb', + 'generic-iso', + 'generic-usb', + ], + 'reservable': + [ 'node-preview', + 'node-iso', + 'node-usb', + ], + } # compute a new key def compute_key(): @@ -43,18 +50,18 @@ def compute_key(): class GetBootMedium(Method): """ - This method is a redesign based on former, supposedly dedicated, + This method is a redesign based on former, supposedly dedicated, AdmGenerateNodeConfFile - As compared with its ancestor, this method provides a much more detailed + As compared with its ancestor, this method provides a much more detailed interface, that allows to - (*) either just preview the node config file -- in which case + (*) either just preview the node config file -- in which case the node key is NOT recomputed, and NOT provided in the output - (*) or regenerate the node config file for storage on a floppy - that is, exactly what the ancestor method used todo, + (*) or regenerate the node config file for storage on a floppy + that is, exactly what the ancestor method used todo, including renewing the node's key (*) or regenerate the config file and bundle it inside an ISO or USB image - (*) or just provide the generic ISO or USB boot images + (*) or just provide the generic ISO or USB boot images in which case of course the node_id_or_hostname parameter is not used action is expected among the following string constants according the @@ -68,42 +75,38 @@ class GetBootMedium(Method): (*) generic-iso (*) generic-usb - for a 'dummynet' node: - (*) node-preview - (*) dummynet-iso - (*) dummynet-usb - Apart for the preview mode, this method generates a new node key for the specified node, effectively invalidating any old boot medium. + Note that 'reservable' nodes do not support 'node-floppy', + 'generic-iso' nor 'generic-usb'. In addition, two return mechanisms are supported. - (*) The default behaviour is that the file's content is returned as a + (*) The default behaviour is that the file's content is returned as a base64-encoded string. This is how the ancestor method used to work. To use this method, pass an empty string as the file parameter. - (*) Or, for efficiency -- this makes sense only when the API is used - by the web pages that run on the same host -- the caller may provide - a filename, in which case the resulting file is stored in that location instead. - The filename argument can use the following markers, that are expanded + (*) Or, for efficiency -- this makes sense only when the API is used + by the web pages that run on the same host -- the caller may provide + a filename, in which case the resulting file is stored in that location instead. + The filename argument can use the following markers, that are expanded within the method - %d : default root dir (some builtin dedicated area under /var/tmp/) Using this is recommended, and enforced for non-admin users - - %n : the node's name when this makes sense, or a mktemp-like name when + - %n : the node's name when this makes sense, or a mktemp-like name when generic media is requested - %s : a file suffix appropriate in the context (.txt, .iso or the like) - %v : the bootcd version string (e.g. 4.0) - %p : the PLC name - %f : the nodefamily - %a : arch - With the file-based return mechanism, the method returns the full pathname - of the result file; + With the file-based return mechanism, the method returns the full pathname + of the result file; ** WARNING ** It is the caller's responsability to remove this file after use. - Options: an optional array of keywords. + Options: an optional array of keywords. options are not supported for generic images - options are not supported for dummynet boxes - Currently supported are + Currently supported are - 'partition' - for USB actions only - 'cramfs' - 'serial' or 'serial:' @@ -112,16 +115,19 @@ class GetBootMedium(Method): tty - baudrate - parity - bits e.g. ttyS0:115200:n:8 - 'variant:' - passed to build.sh as -V + passed to build.sh as -V variants are used to run a different kernel on the bootCD see kvariant.sh for how to create a variant - - 'no-hangcheck' + - 'no-hangcheck' - disable hangcheck + + Tags: the following tags are taken into account when attached to the node: + 'serial', 'cramfs', 'kvariant', 'kargs', 'no-hangcheck' Security: - Non-admins can only generate files for nodes at their sites. - Non-admins, when they provide a filename, *must* specify it in the %d area - Housekeeping: + Housekeeping: Whenever needed, the method stores intermediate files in a private area, typically not located under the web server's accessible area, and are cleaned up by the method. @@ -159,7 +165,7 @@ class GetBootMedium(Method): if len(parts) < 2: raise PLCInvalidArgument, "Node hostname %s is invalid"%node['hostname'] return parts - + # Generate the node (plnode.txt) configuration content. # # This function will create the configuration file a node @@ -193,6 +199,7 @@ class GetBootMedium(Method): # renew the key and save it on the database if renew_key: node['key'] = compute_key() + node.update_last_download(commit=False) node.sync() # Generate node configuration file suitable for BootCD @@ -228,7 +235,7 @@ class GetBootMedium(Method): for setting in settings: if setting['category'] is not None: categories.add(setting['category']) - + for category in categories: category_settings = InterfaceTags(self.api,{'interface_id':interface['interface_id'], 'category':category}) @@ -246,33 +253,34 @@ class GetBootMedium(Method): return file - # see also InstallBootstrapFS in bootmanager that does similar things - def get_nodefamily (self, node): - # get defaults from the myplc build - try: - (pldistro,arch) = file("/etc/planetlab/nodefamily").read().strip().split("-") - except: - (pldistro,arch) = ("planetlab","i386") - - # with no valid argument, return system-wide defaults + # see also GetNodeFlavour that does similar things + def get_nodefamily (self, node, auth): + pldistro = self.api.config.PLC_FLAVOUR_NODE_PLDISTRO + fcdistro = self.api.config.PLC_FLAVOUR_NODE_FCDISTRO + arch = self.api.config.PLC_FLAVOUR_NODE_ARCH if not node: - return (pldistro,arch) + return (pldistro,fcdistro,arch) node_id=node['node_id'] - tag=Nodes(self.api,[node_id],['arch'])[0]['arch'] - if tag: arch=tag - tag=Nodes(self.api,[node_id],['pldistro'])[0]['pldistro'] - if tag: pldistro=tag + # no support for deployment-based BootCD's, use kvariants instead + node_pldistro = GetNodePldistro (self.api).call(auth, node_id) + if node_pldistro: pldistro = node_pldistro + + node_fcdistro = GetNodeFcdistro (self.api).call(auth, node_id) + if node_fcdistro: fcdistro = node_fcdistro + + node_arch = GetNodeArch (self.api).call(auth,node_id) + if node_arch: arch = node_arch - return (pldistro,arch) + return (pldistro,fcdistro,arch) def bootcd_version (self): try: return file(self.BOOTCDDIR + "/build/version.txt").readline().strip() except: raise Exception,"Unknown boot cd version - probably wrong bootcd dir : %s"%self.BOOTCDDIR - + def cleantrash (self): for file in self.trash: if self.DEBUG: @@ -281,7 +289,7 @@ class GetBootMedium(Method): os.unlink(file) ### handle filename - # build the filename string + # build the filename string # check for permissions and concurrency # returns the filename def handle_filename (self, filename, nodename, suffix, arch): @@ -309,7 +317,7 @@ class GetBootMedium(Method): if os.path.exists(filename): raise PLCInvalidArgument, "Resulting file %s already exists"%filename - ### we can now safely create the file, + ### we can now safely create the file, ### either we are admin or under a controlled location filedir=os.path.dirname(filename) # dirname does not return "." for a local filename like its shell counterpart @@ -330,16 +338,16 @@ class GetBootMedium(Method): # regular node, make build's arguments # and build the full command line to be called - if node_type == 'regular': + if node_type in [ 'regular', 'reservable' ]: build_sh_options="" - if "cramfs" in build_sh_spec: + if "cramfs" in build_sh_spec: type += "_cramfs" - if "serial" in build_sh_spec: + if "serial" in build_sh_spec: build_sh_options += " -s %s"%build_sh_spec['serial'] if "variant" in build_sh_spec: build_sh_options += " -V %s"%build_sh_spec['variant'] - + for karg in build_sh_spec['kargs']: build_sh_options += ' -k "%s"'%karg @@ -351,25 +359,11 @@ class GetBootMedium(Method): type, build_sh_options, log_file) - # dummynet node - elif node_type == 'dummynet': - # the build script expect the following parameters: - # the package base directory - # the working directory - # the full path of the configuration file - # the name of the resulting image file - # the type of the generated image - # the name of the log file - command = "%s -b %s -w %s -f %s -o %s -t %s -l %s" \ - % (self.BOOTCDBUILD, self.BOOTCDDIR, self.WORKDIR, - floppy_file, node_image, type, log_file) - command = "touch %s %s; echo 'dummynet build script not yet supported'" \ - % (log_file, node_image) if self.DEBUG: print "The build command line is %s" % command - return command + return command def call(self, auth, node_id_or_hostname, action, filename, options = []): @@ -406,8 +400,27 @@ class GetBootMedium(Method): if options: raise PLCInvalidArgument, "Options are not supported for node configs" else: - # create a dict for build.sh + # create a dict for build.sh build_sh_spec={'kargs':[]} + # use node tags as defaults + # check for node tag equivalents + tags = NodeTags(self.api, + {'node_id': node['node_id'], + 'tagname': ['serial', 'cramfs', 'kvariant', 'kargs', 'no-hangcheck']}, + ['tagname', 'value']) + if tags: + for tag in tags: + if tag['tagname'] == 'serial': + build_sh_spec['serial'] = tag['value'] + if tag['tagname'] == 'cramfs': + build_sh_spec['cramfs'] = True + if tag['tagname'] == 'kvariant': + build_sh_spec['variant'] = tag['value'] + if tag['tagname'] == 'kargs': + build_sh_spec['kargs'] += tag['value'].split() + if tag['tagname'] == 'no-hangcheck': + build_sh_spec['kargs'].append('hcheck_reboot0') + # then options can override tags for option in options: if option == "cramfs": build_sh_spec['cramfs']=True @@ -428,7 +441,7 @@ class GetBootMedium(Method): raise PLCInvalidArgument, "unknown option %s"%option # compute nodename according the action - if action.find("node-") == 0 or action.find("dummynet-") == 0: + if action.find("node-") == 0: nodename = node['hostname'] else: node = None @@ -437,22 +450,16 @@ class GetBootMedium(Method): def hexa2 (c): return chr((c>>4)+65) + chr ((c&16)+65) nodename = "".join(map(hexa2,tempbytes)) - # override some global definition, according node_type - if node_type == 'dummynet': - self.BOOTCDDIR = "/usr/share/dummynet" # the base installation dir - self.BOOTCDBUILD = "/usr/share/dummynet/build.sh" # dummynet build script - self.WORKDIR = "/var/tmp/DummynetBoxMedium" # temporary working dir - # get nodefamily - (pldistro,arch) = self.get_nodefamily(node) - self.nodefamily="%s-%s"%(pldistro,arch) + (pldistro,fcdistro,arch) = self.get_nodefamily(node,auth) + self.nodefamily="%s-%s-%s"%(pldistro,fcdistro,arch) # apply on globals for attr in [ "BOOTCDDIR", "BOOTCDBUILD", "GENERICDIR" ]: setattr(self,attr,getattr(self,attr).replace("@NODEFAMILY@",self.nodefamily)) - + filename = self.handle_filename(filename, nodename, suffix, arch) - + # log call if node: self.message='GetBootMedium on node %s - action=%s'%(nodename,action) @@ -501,12 +508,11 @@ class GetBootMedium(Method): # - build and invoke the build command # - delivery the resulting image file - if action == 'node-iso' or action == 'node-usb' \ - or action == 'dummynet-iso' or action == 'dummynet-usb': + if action == 'node-iso' or action == 'node-usb': ### check we've got required material version = self.bootcd_version() - + if not os.path.isfile(self.BOOTCDBUILD): raise PLCAPIError, "Cannot locate bootcd/build.sh script %s"%self.BOOTCDBUILD @@ -517,7 +523,7 @@ class GetBootMedium(Method): os.chmod(self.WORKDIR,0777) except: raise PLCPermissionDenied, "Could not create dir %s"%self.WORKDIR - + try: # generate floppy config floppy_text = self.floppy_contents(node,True) @@ -547,7 +553,7 @@ class GetBootMedium(Method): if not os.path.isfile (node_image): raise PLCAPIError,"Unexpected location of build.sh output - %s"%node_image - + # handle result if filename: ret=os.system('mv "%s" "%s"'%(node_image,filename)) @@ -565,7 +571,6 @@ class GetBootMedium(Method): except: self.cleantrash() raise - + # we're done here, or we missed something raise PLCAPIError,'Unhandled action %s'%action -