Пример #1
0
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()
Пример #2
0
    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
Пример #3
0
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)
Пример #4
0
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)
Пример #5
0
    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)
Пример #6
0
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)
Пример #7
0
    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
Пример #8
0
    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
Пример #9
0
    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)