def startfield(args): this_hostname = Platform().hostname() plandoc = LXCPlanFileDoc(args.lxcplanfile) config = ConfigDictionary() workdir = config.get('etce', 'WORK_DIRECTORY') if not os.path.exists(workdir): raise LXCError('ETCE WORK_DIRECTORY "%s" not found. ' \ 'Please create it before starting.' % workdir) # lockfile lockfilename = \ os.path.join(plandoc.lxc_root_directory(this_hostname), 'etce.lxc.lock') if os.path.isfile(lockfilename): err = 'Detected an active lxc field with root at: %s. ' \ 'Run "etce-lxc stop" first.' % \ plandoc.lxc_root_directory(this_hostname) raise LXCError(err) startlxcs(plandoc, args.writehosts, args.forcelxcroot, args.dryrun) if not args.dryrun: shutil.copy(args.lxcplanfile, lockfilename) other_hosts = set(plandoc.hostnames()).difference( ['localhost', this_hostname]) # start containers on other hosts, if any if other_hosts: client = None try: client = ClientBuilder().build(\ other_hosts, user=args.user, port=args.port) # push the file and execute client.put(args.lxcplanfile, '.', other_hosts, doclobber=True) # on the destination node the netplan file gets pushed to the # ETCE WORK_DIRECTORY command = 'lxcmanager startlxcs %s writehosts=%s forcelxcroot=%s' \ % (os.path.basename(args.lxcplanfile), args.writehosts, args.forcelxcroot) ret = client.execute(command, other_hosts) for k in ret: print '[%s] return: %s' % (k, ret[k].retval['result']) finally: if client: client.close()
def _collate_container_params(self, containertemplate, commonparams, containerelem, overlays): containerparams = [ ('lxc.utsname', self.lxc_name) ] if containertemplate: containerparams.extend(containertemplate.params) for k,v in commonparams: containerparams.append((k,v)) for paramelem in containerelem.findall('./parameters/parameter'): if(str(paramelem.attrib['name']) == 'lxc.utsname'): # the lxc_name is set by the container element atribute print('Found lxc.utsname in containertemplate. Ignoring', file=sys.stderr) continue containerparams.append((str(paramelem.attrib['name']), str(paramelem.attrib['value']))) for i,parampair in enumerate(containerparams): k,v = parampair try: containerparams[i] = (k,format_string(v, overlays)) except TemplateError as ne: raise LXCError(str(ne)) return containerparams
def stoplxcs(lxcplan): lxcplanfiledoc = lxcplan if not type(lxcplan) == LXCPlanFileDoc: # assume file name lxcplanfiledoc = LXCPlanFileDoc(lxcplan) try: LXCManagerImpl().stop(lxcplanfiledoc) except Exception as e: raise LXCError(e.message)
def startlxcs(lxcplan, writehosts=False, dryrun=False): lxcplanfiledoc = lxcplan if not type(lxcplan) == LXCPlanFileDoc: # assume file name lxcplanfiledoc = LXCPlanFileDoc(lxcplan) try: LXCManagerImpl().start(lxcplanfiledoc, writehosts=writehosts, dryrun=dryrun) except Exception as e: raise LXCError(e.message)
def __init__(self, lxcplanfile): etce.xmldoc.XMLDoc.__init__(self, 'lxcplanfile.xsd') if not os.path.isfile(lxcplanfile): raise LXCError('Cannot find lxcplanfile "%s". Quitting.' % lxcplanfile) self._lxcplanfile = lxcplanfile # just xml parse first self._hostnames, \ self._kernelparameters, \ self._bridges, \ self._containers, \ self._rootdirectories = self._parseplan(lxcplanfile)
def stopfield(args): workdir = ConfigDictionary().get('etce', 'WORK_DIRECTORY') lockfilename = os.path.join(workdir, 'lxcroot', 'etce.lxc.lock') if not os.path.exists(lockfilename) or not os.path.isfile(lockfilename): raise LXCError('Lockfile "%s" not found. Quitting.' % lockfilename) plandoc = LXCPlanFileDoc(lockfilename) other_hosts = set(plandoc.hostnames()).difference( ['localhost', Platform().hostname()]) # stop containers on other hosts, if any try: if other_hosts: client = None try: client = ClientBuilder().build(other_hosts, user=args.user, port=args.port) # push the file and execute client.put(lockfilename, '.', other_hosts, doclobber=True) # on the destination node the netplan file gets pushed to the # ETCE WORK_DIRECTORY command = 'lxcmanager stoplxcs %s' % os.path.basename( lockfilename) ret = client.execute(command, other_hosts) for k in ret: print('[%s] return: %s' % (k, ret[k].retval['result'])) finally: if client: client.close() finally: stoplxcs(plandoc)
def _parseplan(self, lxcplanfile): lxcplanelem = self.parse(lxcplanfile) paramconverter = ParamConverter() kernelparameters = {} containertemplates = {} rootdirectories = {} lxcplanelems = \ lxcplanelem.findall('./containertemplates/containertemplate') for containertemplateelem in lxcplanelems: containertemplate_name = containertemplateelem.attrib['name'] containertemplate_parent_name = \ containertemplateelem.attrib.get('parent', None) containertemplate_parent = None if containertemplate_parent_name: if not containertemplate_parent_name in containertemplates: errmsg = 'parent "%s" of containertemplate "%s" not ' \ 'previously listed. Quitting.' % \ (containertemplate_parent_name, containertemplate_name) raise LXCError(errmsg) containertemplate_parent = \ containertemplates[containertemplate_parent_name] containertemplates[containertemplate_name] = \ ContainerTemplate(containertemplateelem, containertemplate_parent) hostelems = lxcplanelem.findall('./hosts/host') bridges = {} containers = {} hostnames = [] for hostelem in hostelems: hostname = hostelem.attrib.get('hostname') hostnames.append(hostname) # 'localhost' is permitted as a catchall hostname to mean the # local machine only when one host is specified in the file if hostname == 'localhost': if len(hostelems) > 1: error = '"localhost" hostname only permitted when one ' \ 'host is specified. Quitting' raise LXCError(error) # kernel params kernelparameters[hostname] = {} for paramelem in hostelem.findall('./kernelparameters/parameter'): kernelparameters[hostname][paramelem.attrib['name']] = \ paramelem.attrib['value'] # bridges (explicit) bridges[hostname] = {} for bridgeelem in hostelem.findall('./bridges/bridge'): bridge = Bridge(bridgeelem) bridges[hostname][bridge.name] = bridge containers[hostname] = [] params = [] containerselem = hostelem.findall('./containers')[0] root_directory = \ os.path.join(ConfigDictionary().get('etce', 'WORK_DIRECTORY'), 'lxcroot') rootdirectories[hostname] = root_directory # ensure no repeated lxc_indices alllxcids = set([]) for containerelem in hostelem.findall('./containers/container'): containerlxcids = etce.utils.nodestr_to_nodelist( str(containerelem.attrib['lxc_indices'])) repeatedids = alllxcids.intersection(containerlxcids) if len(repeatedids) > 0: error = 'Duplicate lxc_indices {%s} found in LXC Plan File "%s" are not permitted. Quitting.' % \ (','.join([ str(nid) for nid in list(repeatedids) ]), lxcplanfile) raise LXCError(error) alllxcids.update(containerlxcids) # Create containers from container elems for containerelem in hostelem.findall('./containers/container'): templatename = containerelem.attrib.get('template', None) template = containertemplates.get(templatename, None) lxcids = etce.utils.nodestr_to_nodelist( str(containerelem.attrib['lxc_indices'])) # fetch the overlays, use etce file values as default overlays = ConfigDictionary().asdict()['overlays'] for overlayelem in containerelem.findall('./overlays/overlay'): oname = overlayelem.attrib['name'] ovalue = overlayelem.attrib['value'] overlays[oname] = etce.utils.configstrtoval(ovalue) # fetch the overlaylists overlaylists = {} for overlaylistelem in containerelem.findall('./overlays/overlaylist'): oname = overlaylistelem.attrib['name'] separator = overlaylistelem.attrib.get('separator',',') ovalues = overlaylistelem.attrib['values'].split(separator) overlaylists[oname] = ovalues # treat all values for each name as an int if possible, # else all strings for oname,ovals in overlaylists.items(): converted_vals = [] try: converted_vals = [ etce.utils.configstrtoval(oval) for oval in ovals ] overlaylists[oname] = converted_vals except ValueError: # leave as strings pass # Why must a default value be supplied here when # schema declares this attribute with a default value? for i,lxcid in enumerate(lxcids): # start with overlays lxcoverlays = copy.copy(overlays) # then add list items for this node try: for oname,ovals in overlaylists.items(): lxcoverlays[oname] = ovals[i] except IndexError as ie: raise LXCError('No value found for overlay "%s" for lxc_index "%d". Quitting.' \ % (oname, lxcid)) # then lxcindex, lxc_name and lxc_directory (cannot be overwritten) lxcoverlays.update( {'lxc_index':lxcid}) lxcoverlays.update( {'lxc_name':format_string(containerelem.attrib['lxc_name'], lxcoverlays)}) lxcoverlays.update( {'lxc_directory':os.path.join(root_directory, lxcoverlays['lxc_name'])}) containers[hostname].append(Container(containerelem, lxcoverlays, params, template, bridges[hostname], hostname, paramconverter)) # Roll over containers to get names of implicit bridges added # from the container interface bridge names and augment # the bridges list for container in containers[hostname]: for iname,iparams in container.interfaces.items(): if not iname in bridges[hostname]: bridges[hostname][iname] = BridgeImplicit(iname) return hostnames,kernelparameters,bridges,containers,rootdirectories
def _process_interfaces(self, containertemplate, containerelem, overlays): interfaces = defaultdict(lambda: {}) bridge_entry_ipv4 = {} bridge_entry_ipv6 = {} try: if containertemplate: for bridgename,paramdict in containertemplate.interfaces.items(): bridgename = format_string(bridgename, overlays) for iname,ival in paramdict.items(): interfaces[bridgename][format_string(iname, overlays)] = \ format_string(ival, overlays) for bridgename, entryname in \ containertemplate.hosts_entries_ipv4.items(): bridgename = format_string(bridgename, overlays) bridge_entry_ipv4[bridgename] = format_string(entryname, overlays) for bridgename, entryname in \ containertemplate.hosts_entries_ipv6.items(): bridgename = format_string(bridgename, overlays) bridge_entry_ipv6[bridgename] = format_string(entryname, overlays) # overwrite with local values from container for interfaceelem in containerelem.findall('./interfaces/interface'): bridgename = format_string(str(interfaceelem.attrib['bridge']), overlays) interfaceparams = interfaces[bridgename] for iparamelem in interfaceelem.findall('./parameter'): iname = format_string(str(iparamelem.attrib['name']), overlays) ival = format_string(str(iparamelem.attrib['value']), overlays) interfaceparams[iname] = ival entry_name_ipv4 = \ interfaceelem.attrib.get( 'hosts_entry_ipv4', bridge_entry_ipv4.get(bridgename, None)) if entry_name_ipv4: bridge_entry_ipv4[bridgename] = \ format_string(entry_name_ipv4, overlays) entry_name_ipv6 = \ interfaceelem.attrib.get( 'hosts_entry_ipv6', bridge_entry_ipv6.get(bridgename, None)) if entry_name_ipv6: bridge_entry_ipv6[bridgename] = \ format_string(entry_name_ipv6, overlays) except TemplateError as ne: raise LXCError(str(ne)) hosts_entries_ipv4 = [] for bridgename,entry_name_ipv4 in bridge_entry_ipv4.items(): if not 'lxc.network.ipv4' in interfaces[bridgename]: error = 'Found hosts_entry_ipv4 attribute for ' \ 'bridge "%s" for container "%s" but ' \ 'no corresponding "lxc.network.ipv4" ' \ 'value for the interface. Quitting.' \ % (bridgename, self.lxc_name) raise LXCError(error) addr = interfaces[bridgename]['lxc.network.ipv4'] hosts_entries_ipv4.append((entry_name_ipv4, addr.split('/')[0])) hosts_entries_ipv6 = [] for bridgename,entry_name_ipv6 in bridge_entry_ipv6.items(): if not 'lxc.network.ipv6' in interfaces[bridgename]: error = 'Found hosts_entry_ipv6 attribute for ' \ 'bridge "%s" for container "%s" but ' \ 'no corresponding "lxc.network.ipv6" ' \ 'value for the interface. Quitting.' \ % (bridgename, self.lxc_name) raise LXCError(error) addr = interfaces[bridgename]['lxc.network.ipv6'] hosts_entries_ipv6.append((entry_name_ipv6, addr)) return interfaces,hosts_entries_ipv4,hosts_entries_ipv6
def start(self, plandoc, writehosts, forcelxcroot=False, dryrun=False): hostname = socket.gethostname().split('.')[0] lxcrootdir = plandoc.lxc_root_directory(hostname) containers = plandoc.containers(hostname) if not containers: print 'No containers assigned to "%s". Skipping.' % hostname return if not lxcrootdir[0] == '/': print 'root_directory "%s" for hostname "%s" is not an absolute path. Quitting.' % \ (lxcrootdir, hostname) return directory_level = len(lxcrootdir.split('/')) - 1 if not directory_level >= 3: print 'root_directory "%s" for hostname "%s" is less than 3 levels deep. Quitting.' % \ (lxcrootdir, hostname) return allowed_roots = ('tmp', 'opt', 'home', 'var', 'mnt') if not lxcrootdir.split('/')[1] in allowed_roots: print 'root_directory "%s" for hostname "%s" is not located in one of {%s} ' \ 'directory trees. Quitting.' % \ (lxcrootdir, hostname, ', '.join(allowed_roots)) return if lxcrootdir is None or len(containers) == 0: print 'No containers assigned to host %s. Quitting.' % hostname return # delete and remake the node root if os.path.exists(lxcrootdir): if forcelxcroot: print 'Force removal of "%s" lxc root directory.' \ % lxcrootdir shutil.rmtree(lxcrootdir) else: raise LXCError('%s lxc root directory already exists, Quitting.' % lxcrootdir) os.makedirs(lxcrootdir) # set kernelparameters kernelparameters = plandoc.kernelparameters(hostname) if len(kernelparameters) > 0: print 'Setting kernel parameters:' for kernelparamname,kernelparamval in kernelparameters.items(): os.system('sysctl %s=%s' % (kernelparamname,kernelparamval)) # bring up bridge if not dryrun: for _,bridge in plandoc.bridges(hostname).items(): if not bridge.persistent: print 'Bringing up bridge: %s' % bridge.devicename self._platform.bridgeup(bridge.devicename, bridge.addifs, enablemulticastsnooping=True) if not bridge.ipv4 is None: self._platform.adddeviceaddress(bridge.devicename, bridge.ipv4) if not bridge.ipv6 is None: self._platform.adddeviceaddress(bridge.devicename, bridge.ipv6) time.sleep(0.1) elif not self._platform.isdeviceup(bridge.devicename): raise RuntimeError('Bridge %s marked persistent is not up. Quitting.') # write hosts file if not dryrun: if writehosts: self._writehosts(containers) # create container files for container in containers: lxc_directory = container.lxc_directory self._makedirs(lxc_directory) # make the config with open(os.path.join(lxc_directory, 'config'), 'w') as configf: configf.write(str(container)) # make init script filename,initscripttext = container.initscript if initscripttext: scriptfile = os.path.join(lxc_directory, filename) with open(scriptfile, 'w') as sf: sf.write(initscripttext) os.chmod(scriptfile, stat.S_IRWXU | stat.S_IRGRP | \ stat.S_IXGRP | stat.S_IROTH | \ stat.S_IXOTH) if dryrun: print 'dryrun' else: self._startnodes(containers)