+ # no support for deployment-based BootCD's, use kvariants instead
+ node_pldistro = GetNodePldistro (self.api,self.caller).call(auth, node_id)
+ if node_pldistro:
+ pldistro = node_pldistro
+
+ node_fcdistro = GetNodeFcdistro (self.api,self.caller).call(auth, node_id)
+ if node_fcdistro:
+ fcdistro = node_fcdistro
+
+ node_arch = GetNodeArch (self.api,self.caller).call(auth,node_id)
+ if node_arch:
+ arch = node_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 : {}"\
+ .format(self.BOOTCDDIR))
+
+ def cleantrash (self):
+ for file in self.trash:
+ if self.DEBUG:
+ logger.debug('DEBUG -- preserving trash file {}'.format(file))
+ else:
+ os.unlink(file)
+
+ ### handle filename
+ # build the filename string
+ # check for permissions and concurrency
+ # returns the filename
+ def handle_filename (self, filename, nodename, suffix, arch):
+ # allow to set filename to None or any other empty value
+ if not filename: filename=''
+ filename = filename.replace ("%d",self.WORKDIR)
+ filename = filename.replace ("%n",nodename)
+ filename = filename.replace ("%s",suffix)
+ filename = filename.replace ("%p",self.api.config.PLC_NAME)
+ # let's be cautious
+ try: filename = filename.replace ("%f", self.nodefamily)
+ except: pass
+ try: filename = filename.replace ("%a", arch)
+ except: pass
+ try: filename = filename.replace ("%v",self.bootcd_version())
+ except: pass
+
+ ### Check filename location
+ if filename != '':
+ if 'admin' not in self.caller['roles']:
+ if ( filename.index(self.WORKDIR) != 0):
+ raise PLCInvalidArgument("File {} not under {}".format(filename, self.WORKDIR))
+
+ ### output should not exist (concurrent runs ..)
+ # numerous reports of issues with this policy
+ # looks like people sometime suspend/cancel their download
+ # and this leads to the old file sitting in there forever
+ # so, if the file is older than 5 minutes, we just trash
+ grace=5
+ if os.path.exists(filename) and (time.time()-os.path.getmtime(filename)) >= (grace*60):
+ os.unlink(filename)
+ if os.path.exists(filename):
+ raise PLCInvalidArgument(
+ "Resulting file {} already exists - please try again in {} minutes"\
+ .format(filename, grace))
+
+ ### 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
+ if filedir:
+ if not os.path.exists(filedir):
+ try:
+ os.makedirs (filedir,0777)
+ except:
+ raise PLCPermissionDenied("Could not create dir {}".format(filedir))
+
+ return filename
+
+ def build_command(self, nodename, node_type, build_sh_spec, node_image, type, floppy_file):
+ """
+ returns a tuple
+ (*) build command to be run
+ (*) location of the log_file
+ """
+
+ command = ""
+
+ # regular node, make build's arguments
+ # and build the full command line to be called
+ if node_type not in [ 'regular', 'reservable' ]:
+ logger.error("GetBootMedium.build_command: unexpected node_type {}".format(node_type))
+ return command, None
+
+ build_sh_options=""
+ if "cramfs" in build_sh_spec:
+ type += "_cramfs"
+ if "serial" in build_sh_spec:
+ build_sh_options += " -s {}".format(build_sh_spec['serial'])
+ if "variant" in build_sh_spec:
+ build_sh_options += " -V {}".format(build_sh_spec['variant'])
+
+ for karg in build_sh_spec['kargs']:
+ build_sh_options += ' -k "{}"'.format(karg)
+
+ import time
+ date = time.strftime('%Y-%m-%d-%H-%M', time.gmtime())
+ if not os.path.isdir(self.LOGDIR):
+ os.makedirs(self.LOGDIR)
+ log_file = "{}/{}-{}.log".format(self.LOGDIR, date, nodename)
+
+ command = '{} -f "{}" -o "{}" -t "{}" {} > {} 2>&1'\
+ .format(self.BOOTCDBUILD,
+ floppy_file,
+ node_image,
+ type,
+ build_sh_options,
+ log_file)
+
+ logger.info("The build command line is {}".format(command))
+
+ return command, log_file
+
+ def call(self, auth, node_id_or_hostname, action, filename, options = []):
+
+ self.trash=[]
+
+ ### compute file suffix and type
+ if action.find("-iso") >= 0 :
+ suffix = ".iso"
+ type = "iso"
+ elif action.find("-usb") >= 0:
+ suffix = ".usb"
+ type = "usb"
+ else:
+ suffix = ".txt"
+ type = "txt"
+
+ # check for node existence and get node_type
+ nodes = Nodes(self.api, [node_id_or_hostname])
+ if not nodes:
+ raise PLCInvalidArgument("No such node {}".format(node_id_or_hostname))
+ node = nodes[0]
+
+ logger.info("GetBootMedium: {} requested on node {}. Node type is: {}"\
+ .format(action, node['node_id'], node['node_type']))
+
+ # check the required action against the node type
+ node_type = node['node_type']
+ if action not in allowed_actions[node_type]:
+ raise PLCInvalidArgument("Action {} not valid for {} nodes, valid actions are {}"\
+ .format(action, node_type, "|".join(allowed_actions[node_type])))
+
+ # handle / canonicalize options
+ if type == "txt":
+ if options:
+ raise PLCInvalidArgument("Options are not supported for node configs")
+ else:
+ # 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', 'systemd-debug' ]},
+ ['tagname', 'value'])
+ if tags:
+ for tag in tags:
+ if tag['tagname'] == 'serial':
+ build_sh_spec['serial'] = tag['value']
+ elif tag['tagname'] == 'cramfs':
+ build_sh_spec['cramfs'] = True
+ elif tag['tagname'] == 'kvariant':
+ build_sh_spec['variant'] = tag['value']
+ elif tag['tagname'] == 'kargs':
+ build_sh_spec['kargs'] += tag['value'].split()
+ elif tag['tagname'] == 'no-hangcheck':
+ build_sh_spec['kargs'].append('hcheck_reboot0')
+ elif tag['tagname'] == 'systemd-debug':
+ build_sh_spec['kargs'].append('systemd.log_level=debug')
+ build_sh_spec['kargs'].append('systemd.log_target=console')
+ # then options can override tags
+ for option in options:
+ if option == "cramfs":
+ build_sh_spec['cramfs']=True
+ elif option == 'partition':
+ if type != "usb":
+ raise PLCInvalidArgument("option 'partition' is for USB images only")
+ else:
+ type="usb_partition"
+ elif option == "serial":
+ build_sh_spec['serial']='default'
+ elif option.find("serial:") == 0:
+ build_sh_spec['serial']=option.replace("serial:","")
+ elif option.find("variant:") == 0:
+ build_sh_spec['variant']=option.replace("variant:","")
+ elif option == "no-hangcheck":
+ build_sh_spec['kargs'].append('hcheck_reboot0')
+ elif option == "systemd-debug":
+ build_sh_spec['kargs'].append('systemd.log_level=debug')
+ else:
+ raise PLCInvalidArgument("unknown option {}".format(option))
+
+ # compute nodename according the action
+ if action.find("node-") == 0:
+ nodename = node['hostname']
+ else:
+ node = None
+ # compute a 8 bytes random number
+ tempbytes = random.sample (xrange(0,256), 8);
+ def hexa2 (c): return chr((c>>4)+65) + chr ((c&16)+65)
+ nodename = "".join(map(hexa2,tempbytes))
+
+ # get nodefamily
+ (pldistro,fcdistro,arch) = self.get_nodefamily(node, auth)
+ self.nodefamily="{}-{}-{}".format(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 {} - action={}'.format(nodename, action)
+ self.event_objects={'Node': [ node ['node_id'] ]}
+ else:
+ self.message='GetBootMedium - generic - action={}'.format(action)
+
+ ### generic media
+ if action == 'generic-iso' or action == 'generic-usb':
+ if options:
+ raise PLCInvalidArgument("Options are not supported for generic images")
+ # this raises an exception if bootcd is missing
+ version = self.bootcd_version()
+ generic_name = "{}-BootCD-{}{}".format(self.api.config.PLC_NAME, version, suffix)
+ generic_path = "{}/{}".format(self.GENERICDIR, generic_name)
+
+ if filename:
+ ret=os.system ('cp "{}" "{}"'.format(generic_path, filename))
+ if ret==0:
+ return filename
+ else:
+ raise PLCPermissionDenied("Could not copy {} into {}"\
+ .format(generic_path, filename))
+ else:
+ ### return the generic medium content as-is, just base64 encoded
+ return base64.b64encode(file(generic_path).read())
+
+ ### config file preview or regenerated
+ if action == 'node-preview' or action == 'node-floppy':
+ renew_key = (action == 'node-floppy')
+ floppy = self.floppy_contents (node,renew_key)
+ if filename:
+ try:
+ file(filename,'w').write(floppy)
+ except:
+ raise PLCPermissionDenied("Could not write into {}".format(filename))
+ return filename
+ else:
+ return floppy
+
+ ### we're left with node-iso and node-usb
+ # the steps involved in the image creation are:
+ # - create and test the working environment
+ # - generate the configuration file
+ # - build and invoke the build command
+ # - delivery the resulting image file
+
+ 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 {}".format(self.BOOTCDBUILD))
+
+ # create the workdir if needed
+ if not os.path.isdir(self.WORKDIR):
+ try:
+ os.makedirs(self.WORKDIR,0777)
+ os.chmod(self.WORKDIR,0777)
+ except:
+ raise PLCPermissionDenied("Could not create dir {}".format(self.WORKDIR))
+
+ try:
+ # generate floppy config
+ floppy_text = self.floppy_contents(node, True)
+ # store it
+ floppy_file = "{}/{}.txt".format(self.WORKDIR, nodename)
+ try:
+ file(floppy_file,"w").write(floppy_text)
+ except:
+ raise PLCPermissionDenied("Could not write into {}".format(floppy_file))
+
+ self.trash.append(floppy_file)
+
+ node_image = "{}/{}{}".format(self.WORKDIR, nodename, suffix)
+
+ command, log_file = self.build_command(nodename, node_type, build_sh_spec,
+ node_image, type, floppy_file)
+
+ # invoke the image build script
+ if command != "":
+ ret = os.system(command)
+
+ if ret != 0:
+ raise PLCAPIError("{} failed Command line was: {} See logs in {}"\
+ .format(self.BOOTCDBUILD, command, log_file))
+
+ if not os.path.isfile (node_image):
+ raise PLCAPIError("Unexpected location of build.sh output - {}".format(node_image))
+
+ # handle result
+ if filename:
+ ret = os.system('mv "{}" "{}"'.format(node_image, filename))
+ if ret != 0:
+ self.trash.append(node_image)
+ self.cleantrash()
+ raise PLCAPIError("Could not move node image {} into {}"\
+ .format(node_image, filename))
+ self.cleantrash()
+ return filename
+ else:
+ result = file(node_image).read()
+ self.trash.append(node_image)
+ self.cleantrash()
+ logger.info("GetBootMedium - done with build.sh")
+ encoded_result = base64.b64encode(result)
+ logger.info("GetBootMedium - done with base64 encoding - lengths: raw={} - b64={}"
+ .format(len(result), len(encoded_result)))
+ return encoded_result
+ except:
+ self.cleantrash()
+ raise
+
+ # we're done here, or we missed something
+ raise PLCAPIError('Unhandled action {}'.format(action))