How to use grubby_install method in autotest

Best Python code snippet using autotest_python Github


Full Screen

...548 executable = find_executable(path)549 if executable is None:550'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'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) 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) 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 = 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 = 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, 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/ #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 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 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 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 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( 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( 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( 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'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'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.log.error('Parameter to info is required')1732 return -11733 info_index = self.opts.info1734 if not ((info_index.lower() == 'all') or1735 ( self.log.error('Parameter to info should be either "all", "ALL" '1737 'or an integer index')...

Full Screen

Full Screen

Automation Testing Tutorials

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.

LambdaTest Learning Hubs:


You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run autotest automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?