def _read_local_rpm(self, path): # Read the rpm header from the local file hdr = None try: with open(path, 'rb') as rpm_fd: ts = rpm.TransactionSet() # Don't check package signatures before installation, which will # fail unless we have the signing key for the target OS # installed locally. We implicitly trust these packages. ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES) hdr = ts.hdrFromFdno(rpm_fd) return Package(hdr[u'NAME'], hdr[u'EPOCH'], hdr[u'VERSION'], hdr[u'RELEASE'], hdr[u'ARCH']) except IOError as e: # Display an additional warning to the user for any error other # than file missing if e.errno != errno.ENOENT: self._logger.warn(_(u'Unable to open local file ' u'{path}: {error}'). format(path=path, error=e.message)) except rpm.error as e: self._logger.warn(_(u'Unable to read rpm package header from ' u'{path}').format(path=path)) return None
def _get_default(): '''Return the grub path of the default kernel, or None if there is no valid default kernel''' try: default_str = h.aug_get(u'/files{}/default'.format(grub_conf)) except GuestFSException: return None # We don't care if there is no default try: default_int = int(default_str) except ValueError: self._logger.info(_(u'{path} contains invalid default: ' u'{default'). format(path=grub_conf, default=default_str)) return None # Grub indices are zero-based, augeas is 1-based default_aug = (u'/files{}/title[{}]/kernel'. format(grub_conf, default_int + 1)) try: return h.aug_get(default_aug) except GuestFSException: self._logger.info(_(u'grub refers to default kernel {index}, ' u'which doesn\'t exist'). format(index=default_int)) return None
def _remove_libs(self): h = self._h replaced = [] with Network(h): for lib in self._vmw_libs: nevra = str(Package.from_guestfs_app(lib)) name = lib[u'app2_name'] # Get the list of provides for the library package. try: provides = set([i.strip() for i in h.command_lines([u'rpm', u'-q', u'--provides', nevra]) # The packages explicitly provide # themselves. Filter this out if name not in i]) except GuestfsException as ex: self._logger.warn(_(u'Error getting rpm provides for ' u'{package}: {error}'). format(package = nevra, error = ex.message)) continue # Install the dependencies with yum. We use yum explicitly # here, as up2date wouldn't work anyway and local install is # impractical due to the large number of required # dependencies out of our control. try: alts = set([i.strip() for i in h.command_lines(list(chain([u'yum', u'-q', u'resolvedep'], provides)))]) except GuestfsException as ex: self._logger.warn( _(u'Error resolving depencies for ' u'{packages}: {error}'). format(packages = u', '.join(list(provides)), error = ex.message)) continue if len(alts) > 0: try: h.command(u'yum', u'install', u'-y', list(alts)) except GuestfsException as ex: self._logger.warn( _(u'Error installing replacement packages for ' u'{package} ({replacements}): {error}'). format(package = nevra, replacements = u', '.join(list(alts)), error = ex.message)) continue replaced.append(nevra) return replaced
def _get_installed(self, name, arch=None): if arch is None: search = name else: search = u'{}.{}'.format(name, arch) rpmcmd = [u'rpm', u'-q', u'--qf', ur'%{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n', search] try: output = self._h.command_lines(rpmcmd) except GuestFSException: # RPM command returned non-zero. This might be because there was # actually an error, or might just be because the package isn't # installed. # Unfortunately, rpm sent its error to stdout instead of stderr, # and command_lines only gives us stderr in $@. To get round this # we execute the command again, sending all output to stdout and # ignoring failure. If the output contains 'not installed', we'll # assume it's not a real error. cmd = (u'LANG=C ' + u' '.join([u"'"+i+u"'" for i in rpmcmd]) + u' 2>&1 ||:') error = self._h.sh(cmd) if re.search(ur'not installed', error): return raise ConversionError( _(u'Error running {command} in guest: {msg}'). format(command=cmd, msg=error))
def _cap_missing_deps(self, name): h = self._h root = self._root db = self._db arch = h.inspect_get_arch(root) missing = [] cap = db.match_capability(name, arch, h, root) if cap is None: self._logger.debug(u'No {} capability found for this root'. format(name)) return [] for (pkg, params) in cap.iteritems(): try: target = Package(pkg, evr=params[u'minversion']) except Package.InvalidEVR: self._logger.info(_(u'Ignoring invalid minversion for package ' u'{name} in virtio capability: {version}'). format(name=pkg, version=params[u'minversion'])) target = Package(pkg) need = not params[u'ifinstalled'] for installed in self._get_installed(pkg): if installed < target: need = True if installed >= target: need = False continue if need: missing.append(target) return missing
def inspect(self): info = {} options = [] h = self._h root = self._root info[u'hostname'] = h.inspect_get_hostname(root) info[u'os'] = h.inspect_get_type(root) info[u'distribution'] = h.inspect_get_distro(root) info[u'arch'] = h.inspect_get_arch(root) info[u'version'] = { u'major': h.inspect_get_major_version(root), u'minor': h.inspect_get_minor_version(root) } try: self._bootloader = guestconv.converters.grub.detect( h, root, self, self._logger) except BootLoaderNotFound: raise ConversionError(_(u"Didn't detect a bootloader for root " u'{root}').format(root=self._root)) bl_disk, bl_props = self._bootloader.inspect() return {bl_disk: bl_props}, info, options
def _is_installed(self, apps): h = self._h probe = re.compile(ur'virtualbox-guest-additions(?:-.*)$') self._vbox_apps = [str(Package.from_guestfs_app(i)) for i in apps if probe.match(i[u'app2_name'])] self._vbox_uninstall = None config = u'/var/lib/VBoxGuestAdditions/config' if h.is_file(config): prefix = u'INSTALL_DIR' for line in h.read_lines(config): if line.startswith(prefix): install_dir = line[len(prefix)::] uninstall = install_dir + u'/uninstall.sh' if h.is_file(uninstall): self._vbox_uninstall = uninstall else: self._logger.warn(_(u'VirtualBox config at {path} says ' u'INSTALL_DIR={installdir}, but ' u"{uninstall} doesn't exist"). format(path=config, installdir=install_dir, uninstall=uninstall)) break return len(self._vbox_apps) > 0 or self._vbox_uninstall is not None
def __init__(self, db_paths): self._trees = [] for path in db_paths: try: self._trees.append(ET.parse(path)) except ET.ParseError as e: raise DBParseError(_(u'Parse error in %(path)s: %(error)s') % \ {u'path': path, u'error': e.message})
def _inspect_bootloader(self): for bl in [GrubLegacy, Grub2EFI, Grub2BIOS]: try: return bl(self._h, self._root, self._logger) except BootLoaderNotFound: pass # Try the next one raise ConversionError(_(u"Didn't detect a bootloader for root %(root)s") % {u"root": self._root})
def get_initrd(self, path): for line in h.command_lines([u'grubby', u'--info', path]): m = re.match(u'^initrd=(\S+)', line) if m is not None: return m.group(1) raise ConversionError( _(u"grubby didn't return an initrd for kernel %(kernel)s") % {u'kernel': path})
def _check_and_uniq(path): if path in seen: return False seen.add(path) if not self._h.is_file_opts(path, followsymlinks=True): self._logger.warn(_(u"grub refers to {kernel}, which doesn't " "exist").format(kernel=path)) return False return True
def check_available(self, pkgs): missing = [] required = [] self._resolve_required_deps(map(lambda pkg: pkg.name, pkgs), required, missing) if len(missing) > 0: self._logger.warn( _(u'The following files referenced in the configuration are ' u'required, but missing: {list}'). format(list=u' '.join(missing))) return False return True
def check_available(self, pkgs): for i in pkgs: if self._old_packages: self._logger.info(_(u'Checking package {pkg} is available via ' u'YUM').format(pkg=str(i))) try: self._yum_cmd(i, YumInstaller.LIST) return True except YumInstaller.NoPackage: return False else: # We just want to lookup name.arch name_arch = Package(i.name, arch=i.arch) self._logger.info(_(u'Checking for latest version of {pkg} ' u'available via YUM'). format(pkg=str(name_arch))) output = self._yum_cmd(name_arch, YumInstaller.LIST) for line in output: # Look for output lines starting with package name # containing version and source repo m = re.match(ur'{}\s+(\S+)\s+\S+\s*$'. format(re.escape(str(name_arch))), line) if m is None: continue try: found = Package(i.name, evr=m.group(1)) except Package.InvalidEVR: self._logger.debug(u"{}: doesn't look like evr". format(m.group(1))) continue # Check if this package is new enough self._logger.debug(u'Package {pkg} is available'. format(pkg=str(found))) if found >= i: return True return False
def inspect(self): h = self._h root = self._root info = { u'hostname': h.inspect_get_hostname(root), u'os': h.inspect_get_type(root), u'distribution': h.inspect_get_distro(root), u'arch': h.inspect_get_arch(root), u'version': { u'major': h.inspect_get_major_version(root), u'minor': h.inspect_get_minor_version(root) } } installer = Installer(h, root, self._db, self._logger) # Drivers which are always available graphics = [] network = [ (u'e1000', u'Intel E1000'), (u'rtl8139', u'Realtek 8139') ] block = [ (u'ide-hd', u'IDE'), (u'scsi-hd', u'SCSI') ] console = [ (u'vc', _(u'Kernel virtual console')), (u'serial', _(u'Serial console')) ] options = [ (u'graphics', _(u'Graphics driver'), graphics), (u'network', _(u'Network driver'), network), (u'block', _(u'Block device driver'), block), (u'console', _(u'System Console'), console) ] if self._check_capability(u'virtio', installer): network.append((u'virtio-net', u'VirtIO')) block.append((u'virtio-blk', u'VirtIO')) console.append((u'virtio-serial', _(u'VirtIO Serial'))) if self._check_capability(u'cirrus', installer): graphics.append((u'cirrus-vga', u'Cirrus')) if self._check_capability(u'qxl', installer): graphics.append((u'qxl-vga', u'Spice')) self._bootloader = self._inspect_bootloader() bl_disk, bl_props = self._bootloader.inspect() return {bl_disk: bl_props}, info, options
def convert(self, target): if target != 'grub2-bios': raise ConversionError(_(u'Cannot convert grub2-efi bootloader to ' u'{target}').format(target=target)) # For grub2, we: # Turn the EFI partition into a BIOS Boot Partition # Remove the former EFI partition from fstab # Install the non-EFI version of grub # Install grub2 in the BIOS Boot Partition # Regenerate grub.cfg h = self._h if not self._converter._install_capability('grub2-bios'): raise ConversionError(_(u'Failed to install bios version of grub2')) # Relabel the EFI boot partition as a BIOS boot partition h.part_set_gpt_type(self.device, 1, u'21686148-6449-6E6F-744E-656564454649') # Delete the fstab entry for the EFI boot partition for node in h.aug_match(u"/files/etc/fstab/*[file = '/boot/efi']"): h.aug_rm(node) try: h.aug_save() except GuestfsException as ex: augeas_error(h, ex) GRUB2_BIOS_CFG = u'/boot/grub2/grub.cfg' h.command([u'grub2-install', self.device]) h.command([u'grub2-mkconfig', u'-o', GRUB2_BIOS_CFG]) return Grub2BIOS(h, self._root, self._converter, self._logger, GRUB2_BIOS_CFG)
def _resolve_default(default): # Is default an index or a menuentry title? try: default_int = int(default) # Get the kernel from the default'th menuentry i = 0 for title, kernel in _list_menuentries(): if i == default_int: return kernel i += 1 self._logger.warn(_(u'Default kernel with index {index} ' u'not found').format(index=default_int)) except ValueError: # Not an integer: find the menuentry with title == default for title, kernel in _list_menuentries(): if title == default: return kernel self._logger.warn(_(u'Default kernel \'{title}\' not ' u'found').format(title=default)) return None
def _remove(self): h = self._h if len(self._vbox_apps) > 0: _remove_applications(h, self._vbox_apps) if self._vbox_uninstall is not None: try: h.command([self._vbox_uninstall]) h.aug_load() except GuestFSException as ex: self._logger.warn(_(u'VirtualBox Guest Additions ' u'were detected, but ' u'uninstallation failed. The ' u'error message was: {error}'). format(error=ex.message))
def _report_missing_app(self, name, arch, missing): h = self._h root = self._root os = h.inspect_get_type(root) distro = h.inspect_get_distro(root) version = u'{}.{}'.format(h.inspect_get_major_version(root), h.inspect_get_minor_version(root)) self._logger.warn(_(u"Didn't find {name} app for os={os} " u'distro={distro} version={version} ' u'arch={arch}'). format(name=name, os=os, distro=distro, version=version, arch=arch)) missing.append(u'app:'+name)
def list_kernels(self): h = self._h grub_conf = self._grub_conf grub_fs = self._grub_fs # List all kernels from grub.conf in the order that grub would try them paths = [] # Try to add the default kernel to the front of the list. This will # fail if there is no default, or if the default specifies a # non-existent kernel. try: default = h.aug_get(u'/files%s/default' % grub_conf) paths.extend( h.aug_match(u'/files%s/title[%s]/kernel' % (grub_conf, default)) ) except GuestFSException: pass # We don't care # Add kernel paths from grub.conf in the order they are listed. This # will add the default kernel twice, but it doesn't matter. try: paths.extend(h.aug_match(u'/files%s/title/kernel' % grub_conf)) except GuestFSException as ex: augeas_error(h, ex) # Fetch the value of all detected kernels, and sanity check them kernels = [] checked = {} for path in paths: if path in checked: continue checked[path] = True try: kernel = grub_fs + h.aug_get(path) except GuestFSException as ex: augeas_error(h, ex) if h.exists(kernel): kernels.append(kernel) else: self._logger.warn(_(u"grub refers to %(kernel)s, which doesn't exist") % {u'kernel': kernel}) return kernels
def convert(self, target): if target != 'grub-bios': raise ConversionError(_(u'Cannot convert grub-efi bootloader to ' u'{target}').format(target=target)) grub_conf = u'/boot/grub/grub.conf' h = self._h h.cp(self._cfg, grub_conf) h.ln_sf(grub_conf, u'/etc/grub.conf') # Reload to push up grub.conf in its new location h.aug_load() h.command([u'grub-install', self.device]) return GrubBIOS(self._h, self._root, self._converter, self._logger, grub_conf)
def match_app(self, name, arch, h, root): """Match the app with name and arch for the given root.""" app, path_root = self._match_element(u'app', name, arch, h, root) if app is None: return (None, None) paths = app.xpath(u'path[1]') if len(paths) == 0: raise DBParseError(_(u'app {name} for root {root} is missing ' u'a path element'). format(name=name, root=root)) if path_root: path = os.path.join(path_root, paths[0].text.strip()) else: path = paths[0].text.strip() deps = [] for dep in app.xpath(u'dep'): deps.append(dep.text.strip()) return (path, deps)
def _list_menuentries(): '''Return title and kernel for each menuentry in grub config, in order''' lines = iter(h.read_lines(self._cfg)) for line in lines: # Is this a menu entry m = re.match(u"\s*menuentry\s+(?:'([^']*)')?", line) if m is None: continue # Try to find a title title = m.group(1) # May be None # Try to find an open curly while re.search(u'{\s*$', line) is None: try: line = lines.next() except StopIteration: self._logger.warn(_(u'Unexpected EOF in {path}: ' u'menuentry with no \'{\'')) return # line is now the line containing the close curly. # This for loop will continue to iterate starting at the line # following the close curly kernel = None for line in lines: m = re.match(u'\s*linux(?:efi)?\s+(\S+)\s', line) if m is None: if re.search(u'}\s*$', line): break else: continue kernel = m.group(1) # We're now at either a close curly or EOF # title and kernel could both be None yield (title, kernel)
def _remove(self): h = self._h for repo in self._vmw_repos: h.aug_set(repo + u'/enabled', 0) try: h.aug_save() except GuestFSException as ex: augeas_error(h, ex) remove = False if len(self._vmw_libs) > 0: # It's important that we did aug_save() above, or resolvedep might # return the same vmware packages we're trying to get rid of libs = self._remove_libs() else: libs = [] if len(self._vmw_remove) > 0 or len(libs) > 0: _remove_applications(h, chain(self._vmw_remove, libs)) # VMwareTools may have been installed from tarball, in which case the # above won't detect it. Look for the uninstall tool, and run it if # it's present. # # Note that it's important we do this early in the conversion process, # as this uninstallation script naively overwrites configuration files # with versions it cached prior to installation. vmwaretools = u'/usr/bin/vmware-uninstall-tools.pl' if h.is_file(vmwaretools): try: h.command([vmwaretools]) except GuestfsException as ex: self._logger.warn(_(u'VMware Tools was detected, but ' u'uninstallation failed: {error}'). format(error = ex.message)) h.aug_load()
def _check_capability(self, name, installer): h = self._h root = self._root db = self._db arch = h.inspect_get_arch(root) check = [] cap = db.match_capability(name, arch, h, root) if cap is None: self._logger.debug(u'No {} capability found for this root'. format(name)) return False for (pkg, params) in cap.iteritems(): try: target = Package(pkg, evr=params[u'minversion']) except Package.InvalidEVR: self._logger.info(_(u'Ignoring invalid minversion for package ' u'{name} in virtio capability: {version}'). format(name=pkg, version=params[u'minversion'])) target = Package(pkg) need = not params[u'ifinstalled'] for installed in installer.get_installed(pkg): if installed < target: need = True if installed >= target: need = False continue if need: check.append(target) # Success if we've got nothing to check if len(check) == 0: return True return installer.check_available(check)
def __init__(self, h, root, logger, apps): super(HVCitrixFV, self).__init__(u'citrixfv', _(u'Citrix Fully Virtualised'), h, root, logger, apps)
def convert(self, bootloaders, options): self._logger.info(_(u'Converting root %(name)s') % {u'name': self._root})
def _missing_deps(name, missing): '''Utility function for reporting missing dependencies''' l = u', '.join([str(i) for i in missing]) self._logger.info(_(u'Missing dependencies for {name}: {missing}') .format(name=name, missing=l))
def inspect(self): h = self._h root = self._root # Initialise supported drivers options = [ (u'hypervisor', _(u'Hypervisor support'), []), (u'graphics', _(u'Graphics driver'), []), (u'network', _(u'Network driver'), [ (u'e1000', u'Intel E1000'), (u'rtl8139', u'Realtek 8139') ]), (u'block', _(u'Block device driver'), [ (u'ide-hd', u'IDE'), (u'scsi-hd', u'SCSI') ]), (u'console', _(u'System Console'), [ (u'vc', _(u'Kernel virtual console')), (u'serial', _(u'Serial console')) ]) ] drivers = {} for name, desc, values in options: drivers[name] = values def _missing_deps(name, missing): '''Utility function for reporting missing dependencies''' l = u', '.join([str(i) for i in missing]) self._logger.info(_(u'Missing dependencies for {name}: {missing}') .format(name=name, missing=l)) # Detect supported hypervisors self._hypervisors = {} apps = h.inspect_list_applications2(root) for klass in [HVKVM, HVXenPV, HVXenFV, HVVBox, HVVMware, HVCitrixFV, HVCitrixPV]: hv = klass(h, root, self._logger, apps) if hv.is_available(): self._hypervisors[hv.key] = klass drivers[u'hypervisor'].append((hv.key, hv.description)) # Detect supported graphics hardware for driver, desc in [(u'qxl-vga', u'Spice'), (u'cirrus-vga', u'Cirrus')]: deps = self._cap_missing_deps(driver) if len(deps) == 0: drivers[u'graphics'].append((driver, desc)) else: _missing_deps(driver, deps) # Detect VirtIO virtio_deps = self._cap_missing_deps(u'virtio') if len(virtio_deps) == 0: drivers[u'network'].append((u'virtio-net', u'VirtIO')) drivers[u'block'].append((u'virtio-blk', u'VirtIO')) drivers[u'console'].append((u'virtio-serial', _(u'VirtIO Serial'))) else: _missing_deps(u'virtio', virtio_deps) # Info section of inspection info = { u'hostname': h.inspect_get_hostname(root), u'os': h.inspect_get_type(root), u'distribution': h.inspect_get_distro(root), u'arch': h.inspect_get_arch(root), u'version': { u'major': h.inspect_get_major_version(root), u'minor': h.inspect_get_minor_version(root) } } try: self._bootloader = guestconv.converters.grub.detect( h, root, self, self._logger) except BootLoaderNotFound: raise ConversionError(_(u"Didn't detect a bootloader for root " u'{root}').format(root=self._root)) # Persist detected driver support for later sanity checking self._drivers = {} for driver in drivers: self._drivers[driver] = set([i[0] for i in drivers[driver]]) return ({self._bootloader.device: self._bootloader.inspect()}, info, options) raise ConversionError(_(u"Didn't detect a bootloader for root %(root)s") % {u'root': self._root})
def convert(self, bootloaders, options): self._logger.info(_(u"Converting root %(name)s") % {u"name": self._root})
def __init__(self, h, root, logger, apps): super(HVCitrixPV, self).__init__(u'citrixpv', _(u'Citrix Paravirtualised'), h, root, logger, apps)