Best Python code snippet using autotest_python
boottool.py
Source:boottool.py  
...548        executable = find_executable(path)549    if executable is None:550        log.info('Installing grubby because it was not found on this system')551        grubby = Grubby()552        path = grubby.grubby_install()553        installed_grubby = True554    else:555        grubby = Grubby(executable)556        current_version = grubby.get_grubby_version()557        if current_version is None:558            log.error('Could not find version for grubby executable "%s"',559                      executable)560            path = grubby.grubby_install()561            installed_grubby = True562        elif current_version < GRUBBY_REQ_VERSION:563            log.info('Installing grubby because currently installed '564                     'version (%s.%s) is not recent enough',565                     current_version[0], current_version[1])566            path = grubby.grubby_install()567            installed_grubby = True568    if installed_grubby:569        grubby = Grubby(path)570        installed_version = grubby.get_grubby_version_raw()571        log.debug('Installed: %s', installed_version)572class GrubbyInstallException(Exception):573    '''574    Exception that signals failure when doing grubby installation575    '''576    pass577class Grubby(object):578    '''579    Grubby wrapper580    This class calls the grubby binary for most commands, but also581    adds some functionality that is not really suited to be included582    in int, such as boot-once.583    '''584    SUPPORTED_BOOTLOADERS = ('lilo', 'grub2', 'grub', 'extlinux', 'yaboot',585                             'elilo')586    def __init__(self, path=None, opts=None):587        self._set_path(path)588        self.bootloader = None589        self.opts = opts590        self.log = logging.getLogger(self.__class__.__name__)591        if os.environ.has_key('BOOTTOOL_DEBUG_RUN'):592            self.debug_run = True593        else:594            self.debug_run = False595        self._check_grubby_version()596        self._set_bootloader()597    def _set_path(self, path=None):598        """599        Set grubby path.600        If path is not provided, check first if there's a built grubby,601        then look for the system grubby.602        :param path: Alternate grubby path.603        """604        if path is None:605            if os.path.exists(GRUBBY_DEFAULT_USER_PATH):606                self.path = GRUBBY_DEFAULT_USER_PATH607            else:608                self.path = GRUBBY_DEFAULT_SYSTEM_PATH609        else:610            self.path = path611    #612    # The following block contain utility functions that are used to build613    # most of these class methods, such as methods for running commands614    # and preparing grubby command line switches.615    #616    def _check_grubby_version(self):617        '''618        Checks the version of grubby in use and warns if it's not good enough619        '''620        current_version = self.get_grubby_version()621        if current_version is None:622            self.log.warn('Could not detect current grubby version. It may '623                          'be that you are running an unsupported version '624                          'of grubby')625        elif current_version < GRUBBY_REQ_VERSION:626            self.log.warn('version %s.%s being used is not guaranteed to '627                          'work properly. Mininum required version is %s.%s.',628                          current_version[0], current_version[1],629                          GRUBBY_REQ_VERSION[0], GRUBBY_REQ_VERSION[1])630    def _run_get_output(self, arguments):631        '''632        Utility function that runs a command and returns command output633        '''634        if self.debug_run:635            self.log.debug('running: "%s"', ' '.join(arguments))636        result = None637        try:638            result = subprocess.Popen(arguments, shell=False,639                                      stdin=subprocess.PIPE,640                                      stdout=subprocess.PIPE,641                                      close_fds=True).stdout.read()642        except Exception:643            pass644        if result is not None:645            result = result.strip()646            if self.debug_run:647                logging.debug('previous command output: "%s"', result)648        else:649            self.log.error('_run_get_output error while running: "%s"',650                           ' '.join(arguments))651        return result652    def _run_get_output_err(self, arguments):653        '''654        Utility function that runs a command and returns command output655        '''656        if self.debug_run:657            self.log.debug('running: "%s"', ' '.join(arguments))658        result = None659        try:660            result = subprocess.Popen(arguments, shell=False,661                                      stdin=subprocess.PIPE,662                                      stdout=subprocess.PIPE,663                                      stderr=subprocess.PIPE,664                                      close_fds=True).stdout.read()665        except Exception:666            pass667        if result is not None:668            result = result.strip()669            if self.debug_run:670                logging.debug('previous command output/error: "%s"', result)671        else:672            self.log.error('_run_get_output_err error while running: "%s"',673                           ' '.join(arguments))674        return result675    def _run_get_return(self, arguments):676        '''677        Utility function that runs a command and returns status code678        '''679        if self.debug_run:680            self.log.debug('running: "%s"', ' '.join(arguments))681        result = None682        try:683            result = subprocess.call(arguments)684            if self.debug_run:685                logging.debug('previous command result: %s', result)686        except OSError:687            result = -1688            self.log.error('caught OSError, returning %s', result)689        return result690    def _set_bootloader(self, bootloader=None):691        '''692        Attempts to detect what bootloader is installed on the system693        The result of this method is used in all other calls to grubby,694        so that it acts accordingly to the bootloader detected.695        '''696        if bootloader is None:697            result = self.get_bootloader()698            if result is not None:699                self.bootloader = result700        else:701            if bootloader in self.SUPPORTED_BOOTLOADERS:702                self.bootloader = bootloader703            else:704                raise ValueError('Bootloader "%s" is not supported' %705                                 bootloader)706    def _dist_uses_grub2(self):707        if self.get_architecture().startswith('ppc64'):708            (d_name, d_version, d_id) = platform.dist()709            if d_name.lower() == 'redhat' and d_version >= 7:710                return True711            if d_name.lower() == 'suse' and d_version >= 12:712                return True713        return False714    def _run_grubby_prepare_args(self, arguments, include_bootloader=True):715        '''716        Prepares the argument list when running a grubby command717        '''718        args = []719        if self.path is None:720            self._set_path()721        args.append(self.path)722        if self.path is not None and not os.path.exists(self.path):723            self.log.error('grubby executable does not exist: "%s"', self.path)724            if not os.access(self.path, os.R_OK | os.X_OK):725                self.log.error('insufficient permissions (read and execute) '726                               'for grubby executable: "%s"', self.path)727        # If a bootloader has been detected, that is, a mode has been set,728        # it's passed as the first command line argument to grubby729        if include_bootloader and self.bootloader is not None:730            args.append('--%s' % self.bootloader)731        # Override configuration file732        if self.opts is not None and self.opts.config_file:733            args.append('--config-file=%s' % self.opts.config_file)734        elif self._dist_uses_grub2():735            args.append('--config-file=/boot/grub2/grub.cfg')736        args += arguments737        return args738    def _run_grubby_get_output(self, arguments, include_bootloader=True):739        '''740        Utility function that runs grubby with arguments and returns output741        '''742        args = self._run_grubby_prepare_args(arguments, include_bootloader)743        return self._run_get_output(args)744    def _run_grubby_get_return(self, arguments, include_bootloader=True):745        '''746        Utility function that runs grubby with and returns status code747        '''748        args = self._run_grubby_prepare_args(arguments, include_bootloader)749        return self._run_get_return(args)750    def _extract_tarball(self, tarball, directory):751        '''752        Extract tarball into the an directory753        This code assume the first (or only) entry is the main directory754        :type tarball: string755        :param tarball: tarball file path756        :type directory: string757        :param directory: directory path758        :return: path of toplevel directory as extracted from tarball759        '''760        f = tarfile.open(tarball)761        members = f.getmembers()762        topdir = members[0]763        assert topdir.isdir()764        # we can not use extractall() because it is not available on python 2.4765        for m in members:766            f.extract(m, directory)767        return os.path.join(directory, topdir.name)768    def _get_entry_indexes(self, info):769        '''770        Returns the indexes found in a get_info() output771        :type info: list of lines772        :param info: result of utility method get_info()773        :return: maximum index number774        '''775        indexes = []776        for line in self.get_info_lines():777            try:778                key, value = line.split("=")779                if key == 'index':780                    indexes.append(int(value))781            except ValueError:782                pass783        return indexes784    def _index_for_title(self, title):785        '''786        Returns the index of an entry based on the title of the entry787        :type title: string788        :param title: the title of the entry789        :return: the index of the given entry or None790        '''791        if self._is_number(title):792            return title793        info = self.get_info_lines()794        for i in self._get_entry_indexes(info):795            info = self.get_info(i)796            if info is None:797                continue798            lines = info.splitlines()799            looking_for = ('title=%s' % title,800                           'label=%s' % title)801            for line in lines:802                if line in looking_for:803                    return i804        return None805    def _info_filter(self, info, key, value=None):806        '''807        Filters info, looking for keys, optionally set with a given value808        :type info: list of lines809        :param info: result of utility method get_info()810        :type key: string811        :param key: filter based on this key812        :type value: string813        :param value: filter based on this value814        :return: value or None815        '''816        for line in info:817            if value is not None:818                looking_for = '%s=%s' % (key, value)819                if line == looking_for:820                    return line.split("=")[1]821            else:822                if line.startswith("%s=" % key):823                    return line.split("=")[1]824        return None825    def _kernel_for_title(self, title):826        '''827        Returns the kernel path for an entry based on its title828        :type title: string829        :param title: the title of the entry830        :return: the kernel path of None831        '''832        index = self._index_for_title(title)833        if index is not None:834            info = self.get_info_lines(index)835            kernel = self._info_filter(info, 'kernel')836            return kernel837        else:838            return None839    def _is_number(self, data):840        '''841        Returns true if supplied data is an int or string with digits842        '''843        if isinstance(data, int):844            return True845        elif isinstance(data, str) and data.isdigit():846            return True847        return False848    def _get_entry_selection(self, data):849        '''850        Returns a valid grubby parameter for commands such as --update-kernel851        '''852        if self._is_number(data):853            return data854        elif isinstance(data, str) and data.startswith('/'):855            # assume it's the kernel filename856            return data857        elif isinstance(data, str):858            return self._kernel_for_title(data)859        else:860            raise ValueError("Bad value for 'kernel' parameter. Expecting "861                             "either and int (index) or string (kernel or "862                             "title)")863    def _remove_duplicate_cmdline_args(self, cmdline):864        """865        Remove the duplicate entries in cmdline making sure that the first866        duplicate occurrences are the ones removed and the last one remains867        (this is in order to not change the semantics of the "console"868        parameter where the last occurrence has special meaning)869        :param cmdline: a space separate list of kernel boot parameters870            (ex. 'console=ttyS0,57600n8 nmi_watchdog=1')871        :return: a space separated list of kernel boot parameters without872            duplicates873        """874        copied = set()875        new_args = []876        for arg in reversed(cmdline.split()):877            if arg not in copied:878                new_args.insert(0, arg)879                copied.add(arg)880        return ' '.join(new_args)881    #882    # The following methods implement a form of "API" that action methods883    # can rely on. Another goal is to maintain compatibility with the current884    # client side API in autotest (client/shared/boottool.py)885    #886    def get_bootloader(self):887        '''888        Get the bootloader name that is detected on this machine889        This module performs the same action as client side boottool.py890        get_type() method, but with a better name IMHO.891        :return: name of detected bootloader892        '''893        args = [self.path, '--bootloader-probe']894        output = self._run_get_output_err(args)895        if output is None:896            return None897        if output.startswith('grubby: bad argument'):898            return None899        elif output not in self.SUPPORTED_BOOTLOADERS:900            return None901        return output902    # Alias for client side boottool.py API903    get_type = get_bootloader904    # Alias for boottool app905    bootloader_probe = get_bootloader906    def get_architecture(self):907        '''908        Get the system architecture909        This is much simpler version then the original boottool version, that910        does not attempt to filter the result of the command / system call911        that returns the archicture.912        :return: string with system archicteture, such as x86_64, ppc64, etc913        '''914        return os.uname()[4]915    # Alias for boottool app916    arch_probe = get_architecture917    def get_titles(self):918        '''919        Get the title of all boot entries.920        :return: list with titles of boot entries921        '''922        titles = []923        for line in self.get_info_lines():924            try:925                key, value = line.split("=")926                if key in ['title', 'label']:927                    titles.append(value)928            except ValueError:929                pass930        return titles931    def get_default_index(self):932        '''933        Get the default entry index.934        This module performs the same action as client side boottool.py935        get_default() method, but with a better name IMHO.936        :return: an integer with the the default entry.937        '''938        default_index = self._run_grubby_get_output(['--default-index'])939        if default_index is not None and default_index:940            default_index = int(default_index)941        return default_index942    # Alias for client side boottool.py API943    get_default = get_default_index944    # Alias for boottool app945    default = get_default_index946    def set_default_by_index(self, index):947        """948        Sets the given entry number to be the default on every next boot949        To set a default only for the next boot, use boot_once() instead.950        This module performs the same action as client side boottool.py951        set_default() method, but with a better name IMHO.952        Note: both --set-default=<kernel> and --set-default-index=<index>953        on grubby returns no error when it doesn't find the kernel or954        index. So this method will, until grubby gets fixed, always return955        success.956        :param index: entry index number to set as the default.957        """958        return self._run_grubby_get_return(['--set-default-index=%s' % index])959    # Alias for client side boottool.py API960    set_default = set_default_by_index961    def get_default_title(self):962        '''963        Get the default entry title.964        Conforms to the client side boottool.py API, but rely directly on965        grubby functionality.966        :return: a string of the default entry title.967        '''968        return self._run_grubby_get_output(['--default-title'])969    def get_entry(self, search_info):970        """971        Get a single bootloader entry information.972        NOTE: if entry is "fallback" and bootloader is grub973        use index instead of kernel title ("fallback") as fallback is974        a special option in grub975        :param search_info: can be 'default', position number or title976        :return: a dictionary of key->value where key is the type of entry977                information (ex. 'title', 'args', 'kernel', etc) and value978                is the value for that piece of information.979        """980        info = self.get_info(search_info)981        return parse_entry(info)982    def get_entries(self):983        """984        Get all entries information.985        :return: a dictionary of index -> entry where entry is a dictionary986                of entry information as described for get_entry().987        """988        raw = self.get_info()989        entries = {}990        for entry_str in re.split("index", raw):991            if len(entry_str.strip()) == 0:992                continue993            if entry_str.startswith('boot='):994                continue995            if 'non linux entry' in entry_str:996                continue997            entry = parse_entry("index" + entry_str)998            try:999                entries[entry["index"]] = entry1000            except KeyError:1001                continue1002        return entries1003    def get_info(self, entry='ALL'):1004        '''1005        Returns information on a given entry, or all of them if not specified1006        The information is returned as a set of lines, that match the output1007        of 'grubby --info=<entry>'1008        :type entry: string1009        :param entry: entry description, usually an index starting from 01010        :return: set of lines1011        '''1012        command = '--info=%s' % entry1013        info = self._run_grubby_get_output([command])1014        if info:1015            return info1016    def get_title_for_kernel(self, path):1017        """1018        Returns a title for a particular kernel.1019        :param path: path of the kernel image configured in the boot config1020        :return: if the given kernel path is found it will return a string1021                with the title for the found entry, otherwise returns None1022        """1023        entries = self.get_entries()1024        for entry in entries.itervalues():1025            if entry.get('kernel') == path:1026                return entry['title']1027        return None1028    def add_args(self, kernel, args):1029        """1030        Add cmdline arguments for the specified kernel.1031        :param kernel: can be a position number (index) or title1032        :param args: argument to be added to the current list of args1033        """1034        entry_selection = self._get_entry_selection(kernel)1035        command_arguments = ['--update-kernel=%s' % entry_selection,1036                             '--args=%s' % args]1037        self._run_grubby_get_return(command_arguments)1038    def remove_args(self, kernel, args):1039        """1040        Removes specified cmdline arguments.1041        :param kernel: can be a position number (index) or title1042        :param args: argument to be removed of the current list of args1043        """1044        entry_selection = self._get_entry_selection(kernel)1045        command_arguments = ['--update-kernel=%s' % entry_selection,1046                             '--remove-args=%s' % args]1047        self._run_grubby_get_return(command_arguments)1048    def add_kernel(self, path, title='autoserv', root=None, args=None,1049                   initrd=None, default=False, position='end'):1050        """1051        Add a kernel entry to the bootloader (or replace if one exists1052        already with the same title).1053        :param path: string path to the kernel image file1054        :param title: title of this entry in the bootloader config1055        :param root: string of the root device1056        :param args: string with cmdline args1057        :param initrd: string path to the initrd file1058        :param default: set to True to make this entry the default one1059                (default False)1060        :param position: where to insert the new entry in the bootloader1061                config file (default 'end', other valid input 'start', or1062                # of the title)1063        :param xen_hypervisor: xen hypervisor image file (valid only when1064                xen mode is enabled)1065        """1066        if title in self.get_titles():1067            self.remove_kernel(title)1068        parameters = ['--add-kernel=%s' % path, '--title=%s' % title]1069        # FIXME: grubby takes no --root parameter1070        # if root:1071        #     parameters.append('--root=%s' % root)1072        if args:1073            parameters.append('--args=%s' %1074                              self._remove_duplicate_cmdline_args(args))1075        if initrd:1076            parameters.append('--initrd=%s' % initrd)1077        if default:1078            parameters.append('--make-default')1079        # There's currently an issue with grubby '--add-to-bottom' feature.1080        # Because it uses the tail instead of the head of the list to add1081        # a new entry, when copying a default entry as a template1082        # (--copy-default), it usually copies the "recover" entries that1083        # usually go along a regular boot entry, specially on grub2.1084        #1085        # So, for now, until I fix grubby, we'll *not* respect the position1086        # (--position=end) command line option.1087        #1088        # if opts.position == 'end':1089        #     parameters.append('--add-to-bottom')1090        parameters.append("--copy-default")1091        return self._run_grubby_get_return(parameters)1092    def remove_kernel(self, kernel):1093        """1094        Removes a specific entry from the bootloader configuration.1095        :param kernel: entry position or entry title.1096        FIXME: param kernel should also take 'start' or 'end'.1097        """1098        entry_selection = self._get_entry_selection(kernel)1099        if entry_selection is None:1100            self.log.debug('remove_kernel for title "%s" did not find an '1101                           'entry. This is most probably NOT an error', kernel)1102            return 01103        command_arguments = ['--remove-kernel=%s' % entry_selection]1104        return self._run_grubby_get_return(command_arguments)1105    #1106    # The following methods are not present in the original client side1107    # boottool.py1108    #1109    def get_info_lines(self, entry='ALL'):1110        '''1111        Returns information on a given entry, or all of them if not specified1112        The information is returned as a set of lines, that match the output1113        of 'grubby --info=<entry>'1114        :type entry: string1115        :param entry: entry description, usually an index starting from 01116        :return: set of lines1117        '''1118        info = self.get_info(entry)1119        if info:1120            return info.splitlines()1121    def get_grubby_version_raw(self):1122        '''1123        Get the version of grubby that is installed on this machine as is1124        :return: string with raw output from grubby --version1125        '''1126        return self._run_grubby_get_output(['--version'], False)1127    def get_grubby_version(self):1128        '''1129        Get the version of grubby that is installed on this machine1130        :return: tuple with (major, minor) grubby version1131        '''1132        output = self.get_grubby_version_raw()1133        if output is None:1134            self.log.warn('Could not run grubby to fetch its version')1135            return None1136        match = re.match('(grubby version)?(\s)?(\d+)\.(\d+)(.*)', output)1137        if match:1138            groups = match.groups()1139            return (int(groups[2]), int(groups[3]))1140        else:1141            return None1142    def grubby_install_patch_makefile(self):1143        '''1144        Patch makefile, making CFLAGS more forgivable to older toolchains1145        '''1146        cflags_line = 'CFLAGS += $(RPM_OPT_FLAGS) -std=gnu99 -ggdb\n'1147        libs_line = 'grubby_LIBS = -lblkid -lpopt -luuid\n'1148        shutil.move('Makefile', 'Makefile.boottool.bak')1149        o = open('Makefile', 'w')1150        for l in open('Makefile.boottool.bak').readlines():1151            if l.startswith('CFLAGS += '):1152                o.write(cflags_line)1153            elif l.startswith('grubby_LIBS = -lblkid -lpopt'):1154                o.write(libs_line)1155            else:1156                o.write(l)1157        o.close()1158    def grubby_install_backup(self, path):1159        '''1160        Backs up the current grubby binary to make room the one we'll build1161        :type path: string1162        :param path: path to the binary that should be backed up1163        '''1164        backup_path = '%s.boottool.bkp' % path1165        if (os.path.exists(path) and not os.path.exists(backup_path)):1166            try:1167                shutil.move(path, backup_path)1168            except Exception:1169                self.log.warn('Failed to backup the current grubby binary')1170    def grubby_install_fetch_tarball(self, topdir):1171        '''1172        Fetches and verifies the grubby source tarball1173        '''1174        tarball_name = os.path.basename(GRUBBY_TARBALL_URI)1175        # first look in the current directory1176        try:1177            tarball = tarball_name1178            f = open(tarball)1179        except Exception:1180            try:1181                # then the autotest source directory1182                # pylint: disable=E06111183                from autotest.client.shared.settings import settings1184                top_path = settings.get_value('COMMON', 'autotest_top_path')1185                tarball = os.path.join(top_path, tarball_name)1186                f = open(tarball)1187            except Exception:1188                # then try to grab it from github1189                try:1190                    tarball = os.path.join(topdir, tarball_name)1191                    urllib.urlretrieve(GRUBBY_TARBALL_URI, tarball)1192                    f = open(tarball)1193                except Exception:1194                    return None1195        tarball_md5 = md5.md5(f.read()).hexdigest()1196        if tarball_md5 != GRUBBY_TARBALL_MD5:1197            return None1198        return tarball1199    def grubby_build(self, topdir, tarball):1200        '''1201        Attempts to build grubby from the source tarball1202        '''1203        def log_lines(lines):1204            for line in lines:1205                self.log.debug(line.strip())1206        try:1207            find_header('popt.h')1208        except ValueError:1209            self.log.debug('No popt.h header present, skipping build')1210            return False1211        tarball_name = os.path.basename(tarball)1212        srcdir = os.path.join(topdir, 'src')1213        srcdir = self._extract_tarball(tarball, srcdir)1214        os.chdir(srcdir)1215        self.grubby_install_patch_makefile()1216        result = subprocess.Popen(['make'],1217                                  stdout=subprocess.PIPE,1218                                  stderr=subprocess.PIPE)1219        if result.wait() != 0:1220            self.log.debug('Failed to build grubby during "make" step')1221            log_lines(result.stderr.read().splitlines())1222            return False1223        install_root = os.path.join(topdir, 'install_root')1224        os.environ['DESTDIR'] = install_root1225        result = subprocess.Popen(['make', 'install'],1226                                  stdout=subprocess.PIPE,1227                                  stderr=subprocess.PIPE)1228        if result.wait() != 0:1229            self.log.debug('Failed to build grubby during "make install" step')1230            log_lines(result.stderr.read().splitlines())1231            return False1232        return True1233    def grubby_install(self, path=None):1234        '''1235        Attempts to install a recent enough version of grubby1236        So far tested on:1237           * Fedora 16 x86_641238           * Debian 6 x86_641239           * SuSE 12.1 x86_641240           * RHEL 4 on ia64 (with updated python 2.4)1241           * RHEL 5 on ia641242           * RHEL 6 on ppc641243        '''1244        if path is None:1245            if os.geteuid() == 0:1246                path = GRUBBY_DEFAULT_SYSTEM_PATH1247            else:1248                path = GRUBBY_DEFAULT_USER_PATH1249        topdir = tempfile.mkdtemp()1250        deps_klass = DISTRO_DEPS_MAPPING.get(detect_distro_type(), None)1251        if deps_klass is not None:1252            deps = deps_klass()1253            if not deps.check():1254                self.log.warn('Installing distro build deps for grubby. This '1255                              'may take a while, depending on bandwidth and '1256                              'actual number of packages to install')1257                if not deps.install():1258                    self.log.error('Failed to install distro build deps for '1259                                   'grubby')1260        tarball = self.grubby_install_fetch_tarball(topdir)1261        if tarball is None:1262            raise GrubbyInstallException('Failed to fetch grubby tarball')1263        srcdir = os.path.join(topdir, 'src')1264        install_root = os.path.join(topdir, 'install_root')1265        os.mkdir(install_root)1266        if not self.grubby_build(topdir, tarball):1267            raise GrubbyInstallException('Failed to build grubby')1268        self.grubby_install_backup(path)1269        grubby_bin = os.path.join(install_root, 'sbin', 'grubby')1270        inst_dir = os.path.dirname(path)1271        if not os.access(inst_dir, os.W_OK):1272            raise GrubbyInstallException('No permission to copy grubby '1273                                         'binary to directory "%s"' % inst_dir)1274        try:1275            shutil.copy(grubby_bin, path)1276        except Exception:1277            raise GrubbyInstallException('Failed to copy grubby binary to '1278                                         'directory "%s"' % inst_dir)1279        return path1280    def boot_once(self, title=None):1281        '''1282        Configures the bootloader to boot an entry only once1283        This is not implemented by grubby, but directly implemented here, via1284        the 'boot_once_<bootloader>' method.1285        '''1286        self.log.debug('Title chosen to boot once: %s', title)1287        available_titles = self.get_titles()1288        if title not in available_titles:1289            self.log.error('Entry with title "%s" was not found', title)1290            return -11291        default_title = self.get_default_title()1292        self.log.debug('Title actually set as default: %s', default_title)1293        if default_title == title:1294            self.log.info('Doing nothing: entry to boot once is the same as '1295                          'default entry')1296            return1297        else:1298            self.log.debug('Setting boot once for entry: %s', title)1299        bootloader = self.get_bootloader()1300        if bootloader in ('grub', 'grub2', 'elilo'):1301            entry_index = self._index_for_title(title)1302            if entry_index is None:1303                self.log.error('Could not find index for entry with title '1304                               '"%s"', title)1305                return -11306        if bootloader == 'grub':1307            return self.boot_once_grub(entry_index)1308        elif bootloader == 'grub2':1309            return self.boot_once_grub2(entry_index)1310        elif bootloader == 'yaboot':1311            return self.boot_once_yaboot(title)1312        elif bootloader == 'elilo':1313            return self.boot_once_elilo(entry_index)1314        else:1315            self.log.error("Detected bootloader does not implement boot once")1316            return -11317    def boot_once_grub(self, entry_index):1318        '''1319        Implements the boot once feature for the grub bootloader1320        '''1321        # grubonce is a hack present in distros like OpenSUSE1322        grubonce_cmd = find_executable('grubonce')1323        if grubonce_cmd is None:1324            # XXX: check the type of default set (numeric or "saved")1325            grub_instructions = ['savedefault --default=%s --once' %1326                                 entry_index, 'quit']1327            grub_instructions_text = '\n'.join(grub_instructions)1328            grub_binary = find_executable('grub')1329            if grub_binary is None:1330                self.log.error("Could not find the 'grub' binary, aborting")1331                return -11332            p = subprocess.Popen([grub_binary, '--batch'],1333                                 stdin=subprocess.PIPE,1334                                 stdout=subprocess.PIPE,1335                                 stderr=subprocess.PIPE)1336            out, err = p.communicate(grub_instructions_text)1337            complete_out = ''1338            if out is not None:1339                complete_out = out1340            if err is not None:1341                complete_out += "\n%s" % err1342            grub_batch_err = []1343            if complete_out:1344                for l in complete_out.splitlines():1345                    if re.search('error', l, re.IGNORECASE):1346                        grub_batch_err.append(l)1347                if grub_batch_err:1348                    self.log.error("Error while running grub to set boot "1349                                   "once: %s", "\n".join(grub_batch_err))1350                    return -11351            self.log.debug('No error detected while running grub to set boot '1352                           'once')1353            return 01354        else:1355            rc = self._run_get_return([grubonce_cmd, str(entry_index)])1356            if rc:1357                self.log.error('Error running %s', grubonce_cmd)1358            else:1359                self.log.debug('No error detected while running %s',1360                               grubonce_cmd)1361            return rc1362    def boot_once_grub2(self, entry_index):1363        '''1364        Implements the boot once feature for the grub2 bootloader1365        Caveat: this assumes the default set is of type "saved", and not a1366        numeric value.1367        '''1368        default_index_re = re.compile('\s*set\s+default\s*=\s*\"+(\d+)\"+')1369        grub_reboot_names = ['grub-reboot', 'grub2-reboot']1370        grub_reboot_exec = None1371        for grub_reboot in grub_reboot_names:1372            grub_reboot_exec = find_executable(grub_reboot)1373            if grub_reboot_exec is not None:1374                break1375        if grub_reboot_exec is None:1376            self.log.error('Could not find executable among searched names: '1377                           '%s', ' ,'.join(grub_reboot_names))1378            return -11379        grub_set_default_names = ['grub-set-default', 'grub2-set-default']1380        grub_set_default_exec = None1381        for grub_set_default in grub_set_default_names:1382            grub_set_default_exec = find_executable(grub_set_default)1383            if grub_set_default_exec is not None:1384                break1385        if grub_set_default_exec is None:1386            self.log.error('Could not find executable among searched names: '1387                           '%s', ' ,'.join(grub_set_default_names))1388            return -11389        # Make sure the "set default" entry in the configuration file is set1390        # to "${saved_entry}. Assuming the config file is at /boot/grub/grub.cfg1391        deb_grub_cfg_path = '/boot/grub/grub.cfg'1392        if self._dist_uses_grub2():1393            deb_grub_cfg_path = '/boot/grub2/grub.cfg'1394        deb_grub_cfg_bkp_path = '%s.boottool.bak' % deb_grub_cfg_path1395        default_index = None1396        if os.path.exists(deb_grub_cfg_path):1397            shutil.move(deb_grub_cfg_path, deb_grub_cfg_bkp_path)1398            o = open(deb_grub_cfg_path, 'w')1399            for l in open(deb_grub_cfg_bkp_path).readlines():1400                m = default_index_re.match(l)1401                if m is not None:1402                    default_index = int(m.groups()[0])1403                    o.write('set default="${saved_entry}"\n')1404                else:1405                    o.write(l)1406            o.close()1407        # Make the current default entry the "previous saved entry"1408        if default_index is None:1409            default_index = self.get_default_index()1410        else:1411            # grubby adds entries to top. this assumes a new entry to boot once1412            # has already been added to the top, so fallback to the second1413            # entry (index 1) if the boot once entry fails to boot1414            if entry_index == 0:1415                default_index = 11416            else:1417                default_index = 01418        # A negative index is never acceptable1419        if default_index >= 0:1420            prev_saved_return = self._run_get_return([grub_set_default_exec,1421                                                      '%s' % default_index])1422            if prev_saved_return != 0:1423                self.log.error('Could not make entry %s the previous saved entry',1424                               default_index)1425                return prev_saved_return1426        # Finally set the boot once entry1427        return self._run_get_return([grub_reboot_exec,1428                                     '%s' % entry_index])1429    def boot_once_yaboot(self, entry_title):1430        '''1431        Implements the boot once feature for the yaboot bootloader1432        '''1433        nvsetenv_cmd = find_executable('nvsetenv')1434        if nvsetenv_cmd is None:1435            self.log.error("Could not find nvsetenv in PATH")1436            return -11437        return self._run_get_return([nvsetenv_cmd,1438                                     'boot-once',1439                                     entry_title])1440    def boot_once_elilo(self, entry_index):1441        '''1442        Implements boot once for machines with kernel >= 2.61443        This manipulates EFI variables via the interface available at1444        /sys/firmware/efi/vars1445        '''1446        info = self.get_entry(entry_index)1447        kernel = os.path.basename(info['kernel'])1448        # remove quotes1449        args = info['args']1450        if args[0] == '"':1451            args = args[1:]1452        if args[-1] == '"':1453            args = args[:-1]1454        params = "root=%s %s" % (info['root'], args)1455        data = "%s %s" % (kernel, params)1456        efi = EfiToolSys()1457        if not (efi.create_variable('EliloAlt', data)):1458            return -11459        eliloconf = EliloConf()1460        eliloconf.add_global_option('checkalt')1461        eliloconf.add_global_option('initrd', os.path.basename(info['initrd']))1462        eliloconf.remove_global_option('prompt')1463        eliloconf.update()1464        return 01465class OptionParser(optparse.OptionParser):1466    '''1467    Command line option parser1468    Aims to maintain compatibility at the command line level with boottool1469    '''1470    option_parser_usage = '''%prog [options]'''1471    def __init__(self, **kwargs):1472        optparse.OptionParser.__init__(self,1473                                       usage=self.option_parser_usage,1474                                       **kwargs)1475        misc = self.add_option_group('MISCELLANEOUS OPTIONS')1476        misc.add_option('--config-file',1477                        help='Specifies the path and name of the bootloader '1478                        'config file, overriding autodetection of this file')1479        misc.add_option('--force', action='store_true',1480                        help='If specified, any conflicting kernels will be '1481                        'removed')1482        misc.add_option('--bootloader',1483                        help='Manually specify the bootloader to use.  By '1484                        'default, boottool will automatically try to detect '1485                        'the bootloader being used')1486        misc.add_option('--root',1487                        help='The device where the root partition is located')1488        misc.add_option('--debug', default=0,1489                        help='Prints debug messages. This expects a numerical '1490                        'argument corresponding to the debug message '1491                        'verbosity')1492        probe = self.add_option_group('SYSTEM PROBING')1493        probe.add_option('--bootloader-probe', action='store_true',1494                         help='Prints the bootloader in use on the system '1495                         'and exits')1496        probe.add_option('--arch-probe', action='store_true',1497                         help='Prints the arch of the system and exits')1498        actions = self.add_option_group('ACTIONS ON BOOT ENTRIES')1499        actions.add_option('--add-kernel',1500                           help='Adds a new kernel with the given path')1501        actions.add_option('--remove-kernel',1502                           help='Removes the bootloader entry with the given '1503                           'position or title. Also accepts \'start\' or '1504                           '\'end\'')1505        actions.add_option('--update-kernel',1506                           help='Updates an existing kernel with the given '1507                           'position number or title. Useful options when '1508                           'modifying a kernel include --args and '1509                           '--remove-args')1510        actions.add_option('--info',1511                           help='Display information about the bootloader entry '1512                           'at the given position number. Also accepts \'all\' '1513                           'or \'default\'')1514        actions.add_option('--default', action='store_true',1515                           help='Prints the current default kernel for the '1516                           'bootloader')1517        actions.add_option('--set-default',1518                           help='Updates the bootloader to set the default '1519                           'boot entry to given given position or title')1520        actions.add_option('--install', action='store_true',1521                           help='Causes bootloader to update and re-install '1522                           'the bootloader file')1523        actions.add_option('--boot-once', action='store_true',1524                           help='Causes the bootloader to boot the kernel '1525                           'specified by --title just one time, then fall back'1526                           ' to the default entry. This option does not work '1527                           'identically on all architectures')1528        act_args = self.add_option_group('ACTION PARAMETERS')1529        act_args.add_option('--title',1530                            help='The title or label to use for the '1531                            'bootloader entry. Required when adding a new '1532                            'entry.')1533        act_args.add_option('--position',1534                            help='Insert bootloader entry at the given '1535                            'position number, counting from 0. Also accepts '1536                            '\'start\' or \'end\'. Optional when adding a new '1537                            'entry.')1538        act_args.add_option('--make-default', action='store_true',1539                            help='Specifies that the bootloader entry being '1540                            'added should be the new default')1541        kernel = self.add_option_group('LINUX KERNEL PARAMETERS',1542                                       'Options specific to manage boot '1543                                       'entries with Linux')1544        kernel.add_option('--args',1545                          help='Add arguments to be passed to the kernel at '1546                          'boot. Use when adding a new entry or when '1547                          'modifying an existing entry.')1548        kernel.add_option('--remove-args',1549                          help='Arguments to be removed from an existing entry'1550                          '. Use when modifying an existing entry with '1551                          '--update-kernel action.')1552        kernel.add_option('--initrd',1553                          help='The initrd image path to use in the bootloader '1554                          'entry')1555        kernel.add_option('--module',1556                          help='This option adds modules to the new kernel. It'1557                          ' only works with Grub Bootloader. For more module '1558                          'options just add another --module parameter')1559        grubby = self.add_option_group('GRUBBY',1560                                       'Manage grubby, the tool that drives '1561                                       'most of boottool functionality')1562        grubby.add_option('--grubby-version', action='store_true',1563                          help='Prints the version of grubby installed on '1564                          'this machine')1565        grubby.add_option('--grubby-version-check',1566                          help='Checks if the installed version of grubby is '1567                          'recent enough')1568        grubby.add_option('--grubby-install', action='store_true',1569                          help='Attempts to install a recent enought version '1570                          'of grubby')1571        grubby.add_option('--grubby-path',1572                          help='Use a different grubby binary, located at the '1573                          'given path')1574    def opts_has_action(self, opts):1575        '''1576        Checks if (parsed) opts has a first class action1577        '''1578        global ACTIONS_OPT_METHOD_NAME1579        has_action = False1580        for action in ACTIONS_OPT_METHOD_NAME:1581            value = getattr(opts, action)1582            if value is not None:1583                has_action = True1584        return has_action1585    def opts_get_action(self, opts):1586        '''1587        Gets the selected action from the parsed opts1588        '''1589        global ACTIONS_OPT_METHOD_NAME1590        for action in ACTIONS_OPT_METHOD_NAME:1591            value = getattr(opts, action)1592            if value is not None:1593                return action1594        return None1595    def check_values(self, opts, args):1596        '''1597        Validate the option the user has supplied1598        '''1599        # check if an action has been selected1600        if not self.opts_has_action(opts):1601            self.print_help()1602            raise SystemExit1603        # check if action needs a --title option1604        action = self.opts_get_action(opts)1605        if action in ACTIONS_REQUIRE_TITLE:1606            if opts.title is None:1607                print 'Action %s requires a --title parameter' % action1608                raise SystemExit1609        return (opts, args)1610class BoottoolApp(object):1611    '''1612    The boottool application itself1613    '''1614    def __init__(self):1615        self.opts = None1616        self.args = None1617        self.option_parser = OptionParser()1618        self.grubby = None1619        self.log = logging.getLogger(self.__class__.__name__)1620    def _parse_command_line(self):1621        '''1622        Parsers the command line arguments1623        '''1624        (self.opts,1625         self.args) = self.option_parser.parse_args()1626    def _configure_logging(self):1627        '''1628        Configures logging based on --debug= command line switch1629        We do not have as many levels as the original boottool(.pl) had, but1630        we accept the same range of parameters and adjust it to our levels.1631        '''1632        log_map = {0: logging.WARNING,1633                   1: logging.INFO,1634                   2: logging.DEBUG}1635        try:1636            level = int(self.opts.debug)1637        except ValueError:1638            level = 01639        max_level = max(log_map.keys())1640        if level > max_level:1641            level = max_level1642        if os.environ.has_key('BOOTTOOL_DEBUG_RUN'):1643            logging_level = logging.DEBUG1644        else:1645            logging_level = log_map.get(level)1646        logging.basicConfig(level=logging_level,1647                            format=LOGGING_FORMAT)1648    def run(self):1649        self._parse_command_line()1650        self._configure_logging()1651        # if we made this far, the command line checking succeeded1652        if self.opts.grubby_path:1653            self.grubby = Grubby(self.opts.grubby_path, self.opts)1654        else:1655            install_grubby_if_necessary()1656            self.grubby = Grubby(opts=self.opts)1657        if self.opts.bootloader:1658            self.log.debug('Forcing bootloader "%s"', self.opts.bootloader)1659            try:1660                self.grubby._set_bootloader(self.opts.bootloader)1661            except ValueError, msg:1662                self.log.error(msg)1663                sys.exit(-1)1664        #1665        # The following implements a simple action -> method dispatcher1666        # First, we look for a method named action_ + action_name on the1667        # app instance itself. If not found, we try to find a method with1668        # the same name as the action in the grubby instance.1669        #1670        action_name = self.option_parser.opts_get_action(self.opts)1671        try:1672            action_method = getattr(self, "action_%s" % action_name)1673        except AttributeError:1674            action_method = getattr(self.grubby, action_name)1675        if action_method:1676            result = action_method()1677            if result is None:1678                result = 01679            elif isinstance(result, str):1680                print result1681                result = 01682            sys.exit(result)1683    #1684    # The following block implements actions. Actions are methods that will be1685    # called because of user supplied parameters on the command line. Most1686    # actions, such as the ones that query information, are built around the1687    # "API" methods defined in the previous block1688    #1689    def action_grubby_version(self):1690        '''1691        Prints the of grubby that is installed on this machine1692        '''1693        version = self.grubby.get_grubby_version()1694        if version is not None:1695            print "%s.%s" % version1696            return1697        version = self.grubby.get_grubby_version_raw()1698        if version is not None:1699            print version1700    def action_grubby_version_check(self):1701        '''1702        Prints the of grubby that is installed on this machine1703        '''1704        current_version = self.grubby.get_grubby_version()1705        if current_version is None:1706            self.log.warn('Could not get version numbers from grubby')1707            return -11708        required_version = self.opts.grubby_version_check.split('.', 1)1709        required_version_major = required_version[0]1710        if len(required_version) == 1:1711            req_version = (int(required_version_major), 0)1712        else:1713            req_version = (int(required_version_major),1714                           int(required_version[1]))1715        if current_version >= req_version:1716            return 01717        else:1718            return -11719    def action_grubby_install(self):1720        '''1721        Attempts to install a recent enough version of grubby1722        '''1723        return self.grubby.grubby_install()1724    def action_info(self):1725        '''1726        Prints boot entry information1727        boottool is frequently called with 'all' lowercase, but1728        grubby expects it to be uppercase1729        '''1730        if not self.opts.info:1731            self.log.error('Parameter to info is required')1732            return -11733        info_index = self.opts.info1734        if not ((info_index.lower() == 'all') or1735                (self.opts.info.isdigit())):1736            self.log.error('Parameter to info should be either "all", "ALL" '1737                           'or an integer index')...Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
