def _collate_container_params(self, containertemplate, commonparams, containerelem, overlays): containerparams = [ ('docker.utsname', self.docker_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']) == 'docker.utsname'): # the docker_name is set by the container element atribute print >>sys.stderr, \ 'Found docker.utsname in containertemplate. Ignoring' 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 DOCKERError(str(ne)) return containerparams
def stopdockers(dockerplan): dockerplanfiledoc = dockerplan if not type(dockerplan) == DOCKERPlanFileDoc: # assume file name dockerplanfiledoc = DOCKERPlanFileDoc(dockerplan) try: DOCKERManagerImpl().stop(dockerplanfiledoc) except Exception as e: raise DOCKERError(e.message)
def startdockers(dockerplan, writehosts=False, forcedockerroot=False, dryrun=False): dockerplanfiledoc = dockerplan if not type(dockerplan) == DOCKERPlanFileDoc: # assume file name dockerplanfiledoc = DOCKERPlanFileDoc(dockerplan) try: DOCKERManagerImpl().start(dockerplanfiledoc, writehosts=writehosts, forcedockerroot=forcedockerroot, dryrun=dryrun) except Exception as e: raise DOCKERError(e.message)
def __init__(self, dockerplanfile): etce.xmldoc.XMLDoc.__init__(self, 'dockerplanfile.xsd') if not os.path.isfile(dockerplanfile): raise DOCKERError('Cannot find dockerplanfile "%s". Quitting.' % dockerplanfile) self._dockerplanfile = dockerplanfile # just xml parse first self._hostnames, \ self._kernelparameters, \ self._vxlantunnels, \ self._bridges, \ self._containers, \ self._rootdirectories = self._parseplan(dockerplanfile)
def startfield(args): this_hostname = Platform().hostname() config = ConfigDictionary() workdir = config.get('etce', 'WORK_DIRECTORY') workdir = os.getenv('WORKDIR', workdir) if not os.path.exists(workdir): raise DOCKERError('ETCE WORK_DIRECTORY "%s" not found. ' \ 'Please create it before starting.' % workdir) if args.dockerplanfile: dockerplanfile = args.dockerplanfile else: dockerplanfile = os.path.join(workdir, 'dockerplan.xml') plandoc = DOCKERPlanFileDoc(dockerplanfile) # lockfile lockfilename = \ os.path.join(workdir, 'etce.docker.lock') if os.path.isfile(lockfilename): err = 'Detected an active docker field with root at: %s. ' \ 'Run "etce-docker stop" first.' % \ plandoc.docker_root_directory(this_hostname) raise DOCKERError(err) cidr = os.getenv('CIDR', '10.99.0.0/16') containers = [] for hostname, _ in plandoc.hostnames(): for container in plandoc.containers(hostname): for bridgename, interfaceparams in container.interfaces.items(): if IPAddress(interfaceparams['ipv4']) in IPNetwork(cidr): containers.append( (container.docker_name, interfaceparams['ipv4'])) break ipexist = [] for _, ip in containers: ipexist.append(ip) my_ip = '' for ip in IPNetwork(cidr)[1:]: if not str(ip) in ipexist: my_ip = str(ip) break my_ip = my_ip + '/' + cidr.split('/')[1] # write to /etc/hosts in container/machine controller all external ip writehosts(plandoc, containers) hostfile = \ os.path.join(workdir, 'hosts') if not args.dryrun: shutil.copy(dockerplanfile, lockfilename) shutil.copy('/etc/hosts', hostfile) startdockers(plandoc, args.writehosts, args.forcedockerroot, args.dryrun) other_hosts = [] for hostname, ip in plandoc.hostnames(): if hostname != (this_hostname and 'localhost'): other_hosts.append(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, password=args.password) # push the file and execute client.put(dockerplanfile, '.', other_hosts, doclobber=True) # push the file client.put('/etc/hosts', '.', other_hosts, doclobber=True) # on the destination node the netplan file gets pushed to the # ETCE WORK_DIRECTORY command = 'dockermanager startdockers %s writehosts=%s forcedockerroot=%s' \ % (os.path.basename(dockerplanfile), args.writehosts, args.forcedockerroot) ret = client.execute(command, other_hosts) for k in ret: print '[%s] return: %s' % (k, ret[k].retval['result']) finally: if client: client.close() # A valid ETCE Test Directory. TESTDIRECTORY = os.path.join(workdir, 'pub-tdmact') # The output directory to place the built Test Directory. TESTROOT = os.path.join( workdir, TESTDIRECTORY + '_' + etce.utils.timestamp()) os.system('etce-test publish %s %s --verbose' % (TESTDIRECTORY, TESTROOT)) # A user tag to prepend to the name of each test result directory. TESTPREFIX = 'tdmact' # Run scenario order steps #if not args.collect: os.system( 'etce-test run --user root --policy autoadd -v --kill before --nocollect %s %s %s' % (TESTPREFIX, HOSTFILE, TESTROOT))
def stopfield(args): workdir = ConfigDictionary().get('etce', 'WORK_DIRECTORY') workdir = os.getenv('WORKDIR', workdir) lockfilename = os.path.join(workdir, 'etce.docker.lock') if not os.path.exists(lockfilename) or not os.path.isfile(lockfilename): raise DOCKERError('Lockfile "%s" not found. Quitting.' % lockfilename) if args.dockerplanfile: dockerplanfile = args.dockerplanfile else: dockerplanfile = os.path.join(workdir, 'dockerplan.xml') plandoc = DOCKERPlanFileDoc(dockerplanfile) this_hostname = Platform().hostname() other_hosts = [] for hostname, ip in plandoc.hostnames(): if hostname != (this_hostname and 'localhost'): other_hosts.append(hostname) # stop containers on other hosts, if any try: if other_hosts: if args.collect: client_nodes = None try: print 'Collecting results.' time = 'collect_on_%s' % etce.timeutils.getstrtimenow( ).split('.')[0] localtestresultsdir = os.path.join(workdir, 'data', time) field = Field(os.path.join(workdir, 'HOSTFILE')) # root nodes host the filesystem for all of the virtual nodes attached filesystemnodes = list(field.roots()) testdir = 'data' client_nodes = ClientBuilder().build( filesystemnodes, user=args.user, port=args.port, password=args.password, policy=args.policy) try: client_nodes.collect(testdir, localtestresultsdir, filesystemnodes) except: pass finally: if client_nodes: client_nodes.close() client = None try: client = ClientBuilder().build(other_hosts, user=args.user, port=args.port, password=args.password) # 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 = 'dockermanager stopdockers %s' % os.path.basename( dockerplanfile) 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: # os.system('ip link del vxlan1') stopdockers(plandoc) os.system('rm -f %s' % lockfilename)
def start(self, plandoc, writehosts, forcedockerroot=False, dryrun=False): hostname = socket.gethostname().split('.')[0].lower() dockerrootdir = plandoc.docker_root_directory(hostname) containers = plandoc.containers(hostname) if not containers: print 'No containers assigned to "%s". Skipping.' % hostname return if not dockerrootdir[0] == '/': print 'root_directory "%s" for hostname "%s" is not an absolute path. Quitting.' % \ (dockerrootdir, hostname) return directory_level = len(dockerrootdir.split('/')) - 1 if not directory_level >= 3: print 'root_directory "%s" for hostname "%s" is less than 3 levels deep. Quitting.' % \ (dockerrootdir, hostname) return allowed_roots = ('tmp', 'opt', 'home', 'var', 'mnt') if not dockerrootdir.split('/')[1] in allowed_roots: print 'root_directory "%s" for hostname "%s" is not located in one of {%s} ' \ 'directory trees. Quitting.' % \ (dockerrootdir, hostname, ', '.join(allowed_roots)) return if dockerrootdir 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(dockerrootdir): if forcedockerroot: print 'Force removal of "%s" docker root directory.' \ % dockerrootdir shutil.rmtree(dockerrootdir) else: raise DOCKERError('%s docker root directory already exists, Quitting.' % dockerrootdir) os.makedirs(dockerrootdir) # 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)) #vxlan tunnel if not dryrun: for _,vxlantunnel in plandoc.vxlantunnels(hostname).items(): if not self._platform.isdeviceup('vxlan1'): self._platform.runcommand('ip link add %s ' \ 'type vxlan id %s ' \ 'group 239.1.1.1 ' \ 'dev %s' % \ (vxlantunnel.name, vxlantunnel.id, vxlantunnel.device)) self._platform.networkinterfaceup(vxlantunnel.name) # 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.dockerbridgeup(bridge.devicename, bridge.subnet, bridge.iprange, bridge.gateway, bridge.mtu, 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: docker_directory = container.docker_directory self._makedirs(docker_directory) # make the config with open(os.path.join(docker_directory, 'config'), 'w') as configf: configf.write(str(container)) # make init script filename,initscripttext = container.initscript if initscripttext: scriptfile = os.path.join(docker_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)
def _parseplan(self, dockerplanfile): dockerplanelem = self.parse(dockerplanfile) kernelparameters = {} vxlantunnels = {} containertemplates = {} rootdirectories = {} dockerplanelems = \ dockerplanelem.findall('./containertemplates/containertemplate') for containertemplateelem in dockerplanelems: 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 DOCKERError(errmsg) containertemplate_parent = \ containertemplates[containertemplate_parent_name] containertemplates[containertemplate_name] = \ ContainerTemplate(containertemplateelem, containertemplate_parent) hostelems = dockerplanelem.findall('./hosts/host') bridges = {} containers = {} hostnames = [] for hostelem in hostelems: hostname = hostelem.attrib.get('hostname').lower() ip = hostelem.attrib.get('ip') hostnames.append((hostname, ip)) # '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 DOCKERError(error) # kernel params kernelparameters[hostname] = {} for paramelem in hostelem.findall('./kernelparameters/parameter'): kernelparameters[hostname][paramelem.attrib['name']] = \ paramelem.attrib['value'] vxlantunnels[hostname] = {} for vxlantunnelelem in hostelem.findall('./vxlantunnels/vxlantunnel'): vxlantunnel = VXLanTunnel(vxlantunnelelem) vxlantunnels[hostname][vxlantunnel.name] = vxlantunnel # 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'), 'dockerroot') rootdirectories[hostname] = root_directory # ensure no repeated docker_name alldockerids = set([]) # Create containers from container elems for containerelem in hostelem.findall('./containers/container'): templatename = containerelem.attrib.get('template', None) template = containertemplates.get(templatename, None) dockerids = etce.utils.nodestr_to_nodelist( str(containerelem.attrib['docker_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,dockerid in enumerate(dockerids): # start with overlays dockeroverlays = copy.copy(overlays) # then add list items for this node for oname,ovals in overlaylists.items(): dockeroverlays[oname] = ovals[i] # then dockerindex, docker_name and docker_directory (cannot be overwritten) dockeroverlays.update( {'docker_index':dockerid}) dockeroverlays.update( {'docker_name':format_string(containerelem.attrib['docker_name'], dockeroverlays)}) dockeroverlays.update( {'docker_directory':os.path.join(root_directory, dockeroverlays['docker_name'])}) containerdockerids = [str(dockeroverlays['docker_name'])] repeatedids = alldockerids.intersection(containerdockerids) if len(repeatedids) > 0: error = 'Duplicate docker_name {%s} found in DOCKER Plan File "%s" are not permitted. Quitting.' % \ (','.join([str(nid) for nid in list(repeatedids)]), dockerplanfile) raise DOCKERError(error) alldockerids.update(containerdockerids) containers[hostname].append(Container(containerelem, dockeroverlays, params, template, bridges[hostname], hostname)) # 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,vxlantunnels,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 DOCKERError(str(ne)) hosts_entries_ipv4 = [] for bridgename,entry_name_ipv4 in bridge_entry_ipv4.items(): if not 'ipv4' in interfaces[bridgename]: error = 'Found hosts_entry_ipv4 attribute for ' \ 'bridge "%s" for container "%s" but ' \ 'no corresponding "ipv4" ' \ 'value for the interface. Quitting.' \ % (bridgename, self.docker_name) raise DOCKERError(error) addr = interfaces[bridgename]['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 'ipv6' in interfaces[bridgename]: error = 'Found hosts_entry_ipv6 attribute for ' \ 'bridge "%s" for container "%s" but ' \ 'no corresponding "ipv6" ' \ 'value for the interface. Quitting.' \ % (bridgename, self.docker_name) raise DOCKERError(error) addr = interfaces[bridgename]['ipv6'] hosts_entries_ipv6.append((entry_name_ipv6, addr)) return interfaces,hosts_entries_ipv4,hosts_entries_ipv6