def connect_to_server(server=None, port=None): if server is None: server = os.environ.get("COBBLER_SERVER", "") if server == "": raise InfoException("--server must be specified") try_urls = [] if port is None: try_urls = [ "https://%s:443/cobbler_api" % (server), "http://%s:80/cobbler_api" % (server), ] else: try_urls = [ "https://%s:%s/cobbler_api" % (server, port), "http://%s:%s/cobbler_api" % (server, port), ] for url in try_urls: print("- looking for Cobbler at %s" % url) server = __try_connect(url) if server is not None: return server raise InfoException("Could not find Cobbler.")
def create_qemu_image_file(path, size, driver_type): if driver_type not in VALID_DRIVER_TYPES: raise InfoException("Invalid QEMU image type: %s" % driver_type) cmd = ["qemu-img", "create", "-f", driver_type, path, "%sG" % size] try: subprocess_call(cmd) except: traceback.print_exc() raise InfoException("Image file create failed: %s" % string.join(cmd, " "))
def libvirt_enable_autostart(domain_name): import libvirt try: conn = libvirt.open("qemu:///system") conn.listDefinedDomains() domain = conn.lookupByName(domain_name) domain.setAutostart(1) except: raise InfoException("libvirt could not find domain %s" % domain_name) if not domain.autostart: raise InfoException("Could not enable autostart on domain %s." % domain_name)
def urlread(url): """ to support more distributions, implement (roughly) some parts of urlread and urlgrab from urlgrabber, in ways that are less cool and less efficient. """ print("- reading URL: %s" % url) if url is None or url == "": raise InfoException("invalid URL: %s" % url) elif url[0:3] == "nfs": try: ndir = os.path.dirname(url[6:]) nfile = os.path.basename(url[6:]) nfsdir = tempfile.mkdtemp(prefix="koan_nfs", dir="/tmp") nfsfile = os.path.join(nfsdir, nfile) cmd = ["mount", "-t", "nfs", "-o", "ro", ndir, nfsdir] subprocess_call(cmd) fd = open(nfsfile) data = fd.read() fd.close() cmd = ["umount", nfsdir] subprocess_call(cmd) return data except: traceback.print_exc() raise InfoException("Couldn't mount and read URL: %s" % url) elif url[0:4] == "http": try: fd = urllib2.urlopen(url) data = fd.read() fd.close() return data except: traceback.print_exc() raise InfoException("Couldn't download: %s" % url) elif url[0:4] == "file": try: fd = open(url[5:]) data = fd.read() fd.close() return data except: raise InfoException("Couldn't read file from URL: %s" % url) else: raise InfoException("Unhandled URL protocol: %s" % url)
def input_string_or_dict(options, delim=None, allow_multiples=True): """ Older cobbler files stored configurations in a flat way, such that all values for strings. Newer versions of cobbler allow dictionaries. This function is used to allow loading of older value formats so new users of cobbler aren't broken in an upgrade. """ if options is None: return {} elif isinstance(options, list): raise InfoException("No idea what to do with list: %s" % options) elif isinstance(options, type("")): new_dict = {} tokens = string.split(options, delim) for t in tokens: tokens2 = string.split(t, "=") if len(tokens2) == 1: # this is a singleton option, no value key = tokens2[0] value = None else: key = tokens2[0] value = tokens2[1] # if we're allowing multiple values for the same key, # check to see if this token has already been # inserted into the dictionary of values already if key in new_dict.keys() and allow_multiples: # if so, check to see if there is already a list of values # otherwise convert the dictionary value to an array, and add # the new value to the end of the list if isinstance(new_dict[key], list): new_dict[key].append(value) else: new_dict[key] = [new_dict[key], value] else: new_dict[key] = value # dict.pop is not avail in 2.2 if "" in new_dict: del new_dict[""] return new_dict elif isinstance(options, type({})): options.pop('', None) return options else: raise InfoException("invalid input type: %s" % type(options))
def find_vm(conn, vmid): """ Extra bonus feature: vmid = -1 returns a list of everything This function from Func: fedorahosted.org/func """ vms = [] # this block of code borrowed from virt-manager: # get working domain's name ids = conn.listDomainsID() for id in ids: vm = conn.lookupByID(id) vms.append(vm) # get defined domain names = conn.listDefinedDomains() for name in names: vm = conn.lookupByName(name) vms.append(vm) if vmid == -1: return vms for vm in vms: if vm.name() == vmid: return vm raise InfoException("koan could not find the VM to watch: %s" % vmid)
def start_install(name=None, ram=None, disks=None, mac=None, uuid=None, extra=None, vcpus=None, profile_data=None, arch=None, no_gfx=False, fullvirt=True, bridge=None, virt_type=None, virt_auto_boot=False, qemu_driver_type=None, qemu_net_type=None): if "file" in profile_data: raise InfoException("vmware does not work with --image yet") mac = None if "interfaces" not in profile_data: print "- vmware installation requires a system, not a profile" return 1 for iname in profile_data["interfaces"]: intf = profile_data["interfaces"][iname] mac = intf["mac_address"] if mac is None: print "- no MAC information available in this record, cannot install" return 1 print "DEBUG: name=%s" % name print "DEBUG: ram=%s" % ram print "DEBUG: mac=%s" % mac print "DEBUG: disks=%s" % disks # starts vmware using PXE. disk/mem info come from Cobbler # rest of the data comes from PXE which is also intended # to be managed by Cobbler. if not os.path.exists(IMAGE_DIR): os.makedirs(IMAGE_DIR) if not os.path.exists(VMX_DIR): os.makedirs(VMX_DIR) if len(disks) != 1: raise VirtCreateException( "vmware support is limited to 1 virtual disk") disksize = disks[0][1] image = "%s/%s" % (IMAGE_DIR, name) print "- saving virt disk image as %s" % image make_disk(disksize, image) vmx = "%s/%s" % (VMX_DIR, name) print "- saving vmx file as %s" % vmx make_vmx(vmx, image, name, mac, ram) register_vmx(vmx) start_vm(vmx)
def subprocess_call(cmd, ignore_rc=0): """ Wrapper around subprocess.call(...) """ print("- %s" % cmd) rc = subprocess.call(cmd) if rc != 0 and not ignore_rc: raise InfoException("command failed (%s)" % rc) return rc
def make_floppy(autoinst): (fd, floppy_path) = tempfile.mkstemp(suffix='.floppy', prefix='tmp', dir="/tmp") print("- creating floppy image at %s" % floppy_path) # create the floppy image file cmd = "dd if=/dev/zero of=%s bs=1440 count=1024" % floppy_path print("- %s" % cmd) rc = os.system(cmd) if not rc == 0: raise InfoException("dd failed") # vfatify cmd = "mkdosfs %s" % floppy_path print("- %s" % cmd) rc = os.system(cmd) if not rc == 0: raise InfoException("mkdosfs failed") # mount the floppy mount_path = tempfile.mkdtemp(suffix=".mnt", prefix='tmp', dir="/tmp") cmd = "mount -o loop -t vfat %s %s" % (floppy_path, mount_path) print("- %s" % cmd) rc = os.system(cmd) if not rc == 0: raise InfoException("mount failed") # download the autoinst file onto the mounted floppy print("- downloading %s" % autoinst) save_file = os.path.join(mount_path, "unattended.txt") urlgrabber.urlgrab(autoinst, filename=save_file) # umount cmd = "umount %s" % mount_path print("- %s" % cmd) rc = os.system(cmd) if not rc == 0: raise InfoException("umount failed") # return the path to the completed disk image to pass to virt-install return floppy_path
def find_vm(conn, vmid): """ Extra bonus feature: vmid = -1 returns a list of everything This function from Func: fedorahosted.org/func """ vms = get_vms(conn) for vm in vms: if vm.name() == vmid: return vm raise InfoException("koan could not find the VM to watch: %s" % vmid)
def subprocess_get_response(cmd, ignore_rc=False): """ Wrapper around subprocess.check_output(...) """ print "- %s" % cmd rc = 0 result = "" p = subprocess.Popen(cmd, stdout=subprocess.PIPE) result = p.communicate()[0] rc = p.wait() if not ignore_rc and rc != 0: raise InfoException("command failed (%s)" % rc) return rc, result
def _sanitize_nics(nics, bridge, profile_bridge, network_count): ret = [] if network_count is not None and not nics: # Fill in some stub nics so we can take advantage of the loop logic nics = {} for i in range(int(network_count)): nics["foo%s" % i] = { "interface_type": "na", "mac_address": None, "virt_bridge": None, } if not nics: return ret interfaces = sorted(nics.keys()) counter = -1 vlanpattern = re.compile("[a-zA-Z0-9]+\.[0-9]+") for iname in interfaces: counter = counter + 1 intf = nics[iname] if (intf["interface_type"] in ("master", "bond", "bridge", "bonded_bridge_slave") or vlanpattern.match(iname) or iname.find(":") != -1): continue mac = intf["mac_address"] if not bridge: intf_bridge = intf["virt_bridge"] if intf_bridge == "": if profile_bridge == "": raise InfoException( "virt-bridge setting is not defined in cobbler") intf_bridge = profile_bridge else: if bridge.find(",") == -1: intf_bridge = bridge else: bridges = bridge.split(",") intf_bridge = bridges[counter] ret.append((intf_bridge, mac)) return ret
def _sanitize_disks(disks): ret = [] for d in disks: driver_type = None if len(d) > 2: driver_type = d[2] if d[1] != 0 or d[0].startswith("/dev"): ret.append((d[0], d[1], driver_type)) else: raise InfoException( "this virtualization type does not work without a disk image, set virt-size in Cobbler to non-zero" ) return ret
def nfsmount(input_path): # input: [user@]server:/foo/bar/x.img as string # output: (dirname where mounted, last part of filename) as 2-element tuple # FIXME: move this function to util.py so other modules can use it # we have to mount it first filename = input_path.split("/")[-1] dirpath = string.join(input_path.split("/")[:-1], "/") tempdir = tempfile.mkdtemp(suffix='.mnt', prefix='koan_', dir='/tmp') mount_cmd = ["/bin/mount", "-t", "nfs", "-o", "ro", dirpath, tempdir] print("- running: %s" % mount_cmd) rc = subprocess.call(mount_cmd) if not rc == 0: shutil.rmtree(tempdir, ignore_errors=True) raise InfoException("nfs mount failed: %s" % dirpath) # NOTE: option for a blocking install might be nice, so we could do this # automatically, if supported by virt-install print("after install completes, you may unmount and delete %s" % tempdir) return (tempdir, filename)
def build_commandline(uri, name=None, ram=None, disks=None, uuid=None, extra=None, vcpus=None, profile_data=None, arch=None, gfx_type=None, fullvirt=False, bridge=None, virt_type=None, virt_auto_boot=False, virt_pxe_boot=False, qemu_driver_type=None, qemu_net_type=None, qemu_machine_type=None, wait=0, noreboot=False, osimport=False): # Set flags for CLI arguments based on the virtinst_version # tuple above. Older versions of python-virtinst don't have # a version easily accessible, so it will be None and we can # easily disable features based on that (RHEL5 and older usually) disable_autostart = False disable_virt_type = False disable_boot_opt = False disable_driver_type = False disable_net_model = False disable_machine_type = False oldstyle_macs = False oldstyle_accelerate = False if not virtinst_version: print("- warning: old virt-install detected, a lot of features will " "be disabled") disable_autostart = True disable_boot_opt = True disable_virt_type = True disable_driver_type = True disable_net_model = True disable_machine_type = True oldstyle_macs = True oldstyle_accelerate = True import_exists = False # avoid duplicating --import parameter disable_extra = False # disable --extra-args on --import if osimport: disable_extra = True is_import = uri.startswith("import") if is_import: # We use the special value 'import' for imagecreate.py. Since # it is connection agnostic, just let virt-install choose the # best hypervisor. uri = "" fullvirt = None is_xen = uri.startswith("xen") is_qemu = uri.startswith("qemu") if is_qemu: if virt_type != "kvm": fullvirt = True else: fullvirt = None # is libvirt new enough? if not utils.check_version_greater_or_equal(virtinst_version, "0.2.0"): raise InfoException( "need python-virtinst >= 0.2 or virt-install package to do installs for qemu/kvm (depending on your OS)" ) floppy = None cdrom = None location = None importpath = None if is_import: importpath = profile_data.get("file") if not importpath: raise InfoException("Profile 'file' required for image install") elif "file" in profile_data: if is_xen: raise InfoException("Xen does not work with --image yet") # this is an image based installation input_path = profile_data["file"] print("- using image location %s" % input_path) if input_path.find(":") == -1: # this is not an NFS path cdrom = input_path else: (tempdir, filename) = utils.nfsmount(input_path) cdrom = os.path.join(tempdir, filename) autoinst = profile_data.get("autoinst", "") if autoinst != "": # we have a (windows?) answer file we have to provide # to the ISO. print("I want to make a floppy for %s" % autoinst) floppy = utils.make_floppy(autoinst) elif is_qemu or is_xen: # images don't need to source this if "install_tree" not in profile_data: raise InfoException( "Cannot find install source in autoinst file, aborting.") if not profile_data["install_tree"].endswith("/"): profile_data["install_tree"] = profile_data["install_tree"] + "/" location = profile_data["install_tree"] disks = _sanitize_disks(disks) nics = _sanitize_nics(profile_data.get("interfaces"), bridge, profile_data.get("virt_bridge"), profile_data.get("network_count")) if not nics: # for --profile you get one NIC, go define a system if you want more. # FIXME: can mac still be sent on command line in this case? if bridge is None: bridge = profile_data["virt_bridge"] if bridge == "": raise InfoException( "virt-bridge setting is not defined in cobbler") nics = [(bridge, None)] kernel = profile_data.get("kernel_local") initrd = profile_data.get("initrd_local") breed = profile_data.get("breed") os_version = profile_data.get("os_version") if os_version and breed == "ubuntu": os_version = "ubuntu%s" % os_version if os_version and breed == "debian": os_version = "debian%s" % os_version net_model = None disk_bus = None machine_type = None if is_qemu: net_model = qemu_net_type disk_bus = qemu_driver_type machine_type = qemu_machine_type if machine_type is None: machine_type = "pc" cmd = "virt-install " if uri: cmd += "--connect %s " % uri cmd += "--name %s " % name cmd += "--ram %s " % ram cmd += "--vcpus %s " % vcpus if uuid: cmd += "--uuid %s " % uuid if virt_auto_boot and not disable_autostart: cmd += "--autostart " if gfx_type is None: cmd += "--nographics " else: cmd += "--%s " % gfx_type if is_qemu and virt_type: if not disable_virt_type: cmd += "--virt-type %s " % virt_type if is_qemu and machine_type and not disable_machine_type: cmd += "--machine %s " % machine_type if fullvirt or is_qemu or is_import: if fullvirt is not None: cmd += "--hvm " elif oldstyle_accelerate: cmd += "--accelerate " if virt_pxe_boot or is_xen: cmd += "--pxe " elif cdrom: cmd += "--cdrom %s " % cdrom elif location: cmd += "--location %s " % location if is_qemu and extra and not (virt_pxe_boot) and not ( disable_extra): cmd += ("--extra-args=\"%s\" " % (extra)) elif importpath: cmd += "--import " import_exists = True if arch: cmd += "--arch %s " % arch else: cmd += "--paravirt " if not disable_boot_opt: cmd += ("--boot kernel=%s,initrd=%s,kernel_args=\"%s\" " % (kernel, initrd, extra)) else: if location: cmd += "--location %s " % location if extra: cmd += "--extra-args=\"%s\" " % extra if breed and breed != "other": if os_version and os_version != "other": if breed == "suse": suse_version_re = re.compile("^(opensuse[0-9]+)\.([0-9]+)$") if suse_version_re.match(os_version): os_version = suse_version_re.match(os_version).groups()[0] # make sure virt-install knows about our os_version, # otherwise default it to virtio26 or generic26 # found = False if os_version in supported_variants: pass # os_version is correct elif os_version + ".0" in supported_variants: # osinfo based virt-install only knows about major.minor # variants, not just major variants like it used to. Default # to major.0 variant in that case. Lack of backwards # compatibility in virt-install grumble grumble. os_version = os_version + ".0" else: if "virtio26" in supported_variants: os_version = "virtio26" else: os_version = "generic26" print("- warning: virt-install doesn't know this os_version, " "defaulting to %s" % os_version) cmd += "--os-variant %s " % os_version else: distro = "unix" if breed in ["debian", "suse", "redhat"]: distro = "linux" elif breed in ["windows"]: distro = "windows" cmd += "--os-type %s " % distro if importpath: # This needs to be the first disk for import to work cmd += "--disk path=%s " % importpath for path, size, driver_type in disks: print("- adding disk: %s of size %s (driver type=%s)" % (path, size, driver_type)) cmd += "--disk path=%s" % (path) if str(size) != "0": cmd += ",size=%s" % size if disk_bus: cmd += ",bus=%s" % disk_bus if driver_type and not disable_driver_type: cmd += ",format=%s" % driver_type cmd += " " if floppy: cmd += "--disk path=%s,device=floppy " % floppy for bridge, mac in nics: cmd += "--network bridge=%s" % bridge if net_model and not disable_net_model: cmd += ",model=%s" % net_model if mac: if oldstyle_macs: cmd += " --mac=%s" % mac else: cmd += ",mac=%s" % mac cmd += " " cmd += "--wait %d " % int(wait) if noreboot: cmd += "--noreboot " if osimport and not (import_exists): cmd += "--import " cmd += "--noautoconsole " return shlex.split(cmd.strip())
def run(self): """ Commence with the registration already. """ # not really required, but probably best that ordinary users don't try # to run this not knowing what it does. if os.getuid() != 0: raise InfoException("root access is required to register") print("- preparing to koan home") self.conn = utils.connect_to_server(self.server, self.port) reg_info = {} print("- gathering network info") netinfo = utils.get_network_info() reg_info["interfaces"] = netinfo print("- checking hostname") sysname = "" if self.hostname != "" and self.hostname != "*AUTO*": hostname = self.hostname sysname = self.hostname else: hostname = socket.getfqdn() if hostname == "localhost.localdomain": if self.hostname == '*AUTO*': hostname = "" sysname = str(time.time()) else: raise InfoException( "must specify --fqdn, could not discover") if sysname == "": sysname = hostname if self.profile == "": raise InfoException("must specify --profile") # we'll do a profile check here just to avoid some log noise on the remote end. # network duplication checks and profile checks also happen on the # remote end. avail_profiles = self.conn.get_profiles() matched_profile = False for x in avail_profiles: if x.get("name", "") == self.profile: matched_profile = True break reg_info['name'] = sysname reg_info['profile'] = self.profile reg_info['hostname'] = hostname if not matched_profile: raise InfoException( "no such remote profile, see 'koan --list-profiles'") if not self.batch: self.conn.register_new_system(reg_info) print("- registration successful, new system name: %s" % sysname) else: try: self.conn.register_new_system(reg_info) print("- registration successful, new system name: %s" % sysname) except: traceback.print_exc() print("- registration failed, ignoring because of --batch") return