def _healthcheck(self): target = self.target l0 = target.store.list() target.report_pass("got existing list of files", dict(l0 = l0)) tmpname = commonl.mkid(str(id(target))) + ".1" target.store.upload(tmpname, __file__) target.report_pass("uploaded file %s" % tmpname) l = target.store.list() # remove elements we knew existed (there might be other # processes in parallel using this ) l = list(set(l) - set(l0)) if len(l) != 1: raise tc.failed_e( "after uploading one file, %d are listed; expected 1" % len(l), dict(l = l)) if l[0] != tmpname: raise tc.failed_e( "after uploading file, name differs, expected %s" % tmpname, dict(l = l)) target.report_pass("listed afer uploading one") tmpname2 = commonl.mkid(str(id(target))) + ".2" target.store.upload(tmpname2, __file__) target.report_pass("uploaded second file %s" % tmpname2) l = target.store.list() l = list(set(l) - set(l0)) if len(l) != 2: raise tc.failed_e( "after uploading another file, %d are listed, expected 2" % len(l), dict(l = l)) if tmpname2 not in l: raise tc.failed_e( "after uploading file, can't find %s" % tmpname2, dict(l = l)) target.report_pass("listed after uploading second file", dict(l = l)) target.store.delete(tmpname) l = target.store.list() if tmpname in l: raise tc.failed_e( "after removing %s, still can find it in listing" % tmpname, dict(l = l)) target.report_pass("removed %s" % tmpname) target.store.delete(tmpname2) l = target.store.list() if tmpname2 in l: raise tc.failed_e( "after removing %s, still can find it in listing" % tmpname2, dict(l = l)) l = list(set(l) - set(l0)) if l: raise tc.failed_e( "after removing all, list is not empty", dict(l = l)) target.report_pass("all files removed report empty list")
def _healthcheck(self): target = self.target l = target.store.list() print "OK: can do initial list, got", pprint.pformat(l) for name, _md5 in l.items(): target.store.delete(name) print "OK: deleted existing %s" % name tmpname = commonl.mkid(str(id(target))) + ".1" target.store.upload(tmpname, __file__) print "OK: uploaded 1", tmpname l = target.store.list() assert len(l) == 1, \ "after uploading one file, %d are listed, expected 1; got %s" \ % (len(l), pprint.pformat(l)) assert l.keys()[0] == tmpname, \ "after uploading file, name differs, expected %s; got %s" \ % (tmpname, pprint.pformat(l)) print "OK: 1 listed" tmpname2 = commonl.mkid(str(id(target))) + ".2" target.store.upload(tmpname2, __file__) print "OK: uploaded 2", tmpname2 l = target.store.list() assert len(l) == 2, \ "after uploading another file, %d are listed, expected 2; got %s" \ % (len(l), pprint.pformat(l)) assert tmpname2 in l.keys(), \ "after uploading file, can't find %s; got %s" \ % (tmpname2, pprint.pformat(l)) print "OK: 2 listed" target.store.delete(tmpname) l = target.store.list() assert tmpname not in l.keys(), \ "after removing %s, still can find it in listing; got %s" \ % (tmpname, pprint.pformat(l)) print "OK: removed", tmpname target.store.delete(tmpname2) l = target.store.list() assert tmpname2 not in l.keys(), \ "after removing %s, still can find it in listing; got %s" \ % (tmpname2, pprint.pformat(l)) assert len(l) == 0, \ "after removing all, list is not empty; got %s" \ % pprint.pformat(l) print "OK: removed", tmpname2, "list is empty"
def __init__(self, path="/usr/sbin/dnsmasq"): cmdline = [ path, "--keep-in-foreground", "--pid-file=%(path)s/dnsmasq.pid", "--no-hosts", # only files in... "--hostsdir=%(path)s/dnsmasq.hosts", # ..this dir #"--expand-hosts", # FIXME: domain name? "-2", # No DHCP/TFTP (FIXME: will move to use it) # serve only on the in the interface for this network "--listen-address=%(ipv4_addr)s", "--interface=b%(id)s", # FIXME: hardcoded # need to use --bind-interfaces so we only bind to our # interface and we can run multiple dnsmasqa and coexists # with whichever are in the system "--bind-interfaces", "--except-interface=lo", # if a plain name (w/o domain name) is not found in the # local database, do not forward it upstream "--domain-needed", # Needs an A record "%(ipv4_addr)s %(id)s", created in on() "--auth-server=%(id)s," ] ttbl.power.daemon_c.__init__(self, cmdline, precheck_wait=0.5, mkpidfile=False, pidfile="%(path)s/dnsmasq.pid") self.upid_set( "dnsmasq daemon", # if multiple virtual machines are created associated to a # target, this shall generate a different ID for # each...in most cases serial_number=commonl.mkid(" ".join(cmdline)))
def check_user_is_guest(self, user): assert isinstance(user, ttbl.user_control.User) userid = user.get_id() guestid = commonl.mkid(userid, l = 4) if self.get("guest." + guestid) == userid: return True return False
def _component_validate(target, component): # component must be tap-ICNAME # Create a TAP interface to ICNAME # target must be in ICNAME interfaconnce if not component.startswith("tuntap-"): raise ValueError( "%s: can't create TAP interface:" " cannot recognize component name as something we" " can use to create a TAP interface (expect tap-ICNAME)" % component) _, ic_name = component.split("-", 1) # ensure target is a member of icname if ic_name not in target.tags.get('interconnects', {}): raise ValueError( "%s: can't create TAP interface:" " target '%s' is not connected to interconnect '%s'" % (component, target.id, ic_name)) # we need a system unique name, since what this creates is # global to the whole system--we can easily run out of naming # space though, as network interfaces are limited in size -- # this probably could just hash it all, but then tracing would # be a pain... if_name = "t%s%s" % (ic_name, target.id) if len(if_name) >= 15: # Linux's max network interface name is 15 # so we do tHASH if_name = "t" + commonl.mkid(if_name, 6) return if_name, ic_name
def check_userid_is_user_creator_guest(self, userid): assert isinstance(userid, str) guestid = commonl.mkid(userid, l = 4) if userid == self.get("user") or userid == self.get("creator") \ or userid == self.get("guest." + guestid): return True return False
def request(groups, user, obo_user, priority = None, preempt = False, queue = False, reason = None): """ :params list(str) groups: list of groups of targets """ assert isinstance(groups, dict) for group_name, target_list in groups.items(): assert isinstance(group_name, basestring) assert isinstance(target_list, list) # FIXME: verify not empty for target_name in target_list: assert isinstance(target_name, basestring) assert target_is_valid(target_name) user = user._get_current_object() obo_user = obo_user._get_current_object() assert isinstance(user, ttbl.user_control.User), \ "user is %s (%s)" % (user, type(user)) assert isinstance(obo_user, ttbl.user_control.User) if priority != None: assert priority > 0 # FIXME: verify calling user has this priority else: priority = 500 # DEFAULT FROM USER assert isinstance(preempt, bool) assert isinstance(queue, bool) assert reason == None or isinstance(reason, basestring) allocationid = commonl.mkid(obo_user.get_id() + str(time.time())) dirname = os.path.join(path, allocationid) commonl.makedirs_p(dirname + "/guests") commonl.makedirs_p(dirname + "/groups") alloc = one_c(allocationid, dirname) alloc.set("user", obo_user.get_id()) alloc.set("creator", user.get_id()) alloc.timestamp() for group in groups: # FIXME: this is severly limited in size, we need a normal file to set this info with one target per file alloc.set("groups/" + group, " ".join(group)) result = { # { 'busy', 'queued', 'allocated', 'rejected' }, "state": 'rejected', "allocationid": allocationid, #"allocationid": None, # if queued; derived from OWNER's cookie # "not allowed on TARGETNAMEs" # "targets TARGETNAMEs are busy" "message": "not implemented yet" } return result
def __init__(self, location): """ Initialize the database to be saved in the give location directory :param str location: Directory where the database will be kept """ self.location = location assert os.path.isdir(location) \ and os.access(location, os.R_OK | os.W_OK | os.X_OK) self.uuid_seed = commonl.mkid(str(id(self)))
def guest_add(self, userid): # we can't really validate if the user exists, because we # don't have their password to run it across the auth systems; # so we'll just record it's presence and if anyone auths as # that user, then ... it's ok # # we are making the almost reaonsable assumption that the number # of guests will be low (generally < 5) and thus a base32 ID space of # four digits will do more than enough to guarantee there is no # collissions. FLWs. guestid = commonl.mkid(userid, l = 4) self.set("guest." + guestid, userid)
def __init__(self, qemu_cmdline, nic_model="virtio-net-pci"): # Here we initialize part of the command line; the second part # is generated in on(), since the pieces we need are only # known at that time [e.g. network information, images to load]. ttbl.debug.impl_c.__init__(self) qemu_cmdline += [ # Common command line options which are always appended; # there is no way to override these because this driver # depends on them to work properly. # Debug: a QMP socket called qemu.qmp is used to # control the VM. Make sure you tell QEMU to start # it # "-qmp", "unix:%(path)s/qemu.qmp,server,nowait", # Always start in debug mode -- this way the # whole thing is stopped until we unleash it # with QMP; this allows us to first start # daemons that we might need to start "-S", "-gdb", "tcp:0.0.0.0:%(qemu-gdb-tcp-port)s", # PID file that we use to test for the process and kill it "-pidfile", "%(path)s/qemu.%(component)s.pid" ] self.nic_model = nic_model ttbl.power.daemon_c.__init__( self, cmdline=qemu_cmdline, # paranoid asks power inteface to retry if it fails to # power on/off and get power status; this allows QEMU # restarts to be automated if it fails for retryable # conditions such as race condition in port acquisition. paranoid=True, pidfile="%(path)s/qemu.%(component)s.pid", # qemu makes its own PID files mkpidfile=False) # set the power controller part to paranoid mode; this way if # QEMU fails to start for a retryable reason (like port # taken), it will be retried automatically self.power_on_recovery = True self.paranoid_get_samples = 1 self.paranoid = True ttbl.images.impl_c.__init__(self) self.upid_set( "QEMU virtual machine", name="qemu", # if multiple virtual machines are created associated to a # target, this shall generate a different ID for # each...in most cases serial_number=commonl.mkid(" ".join(qemu_cmdline) + nic_model))
def guest_remove(allocid, calling_user, guest): assert isinstance(calling_user, ttbl.user_control.User) if not isinstance(guest, str): return { "state": "rejected", "_message": "guest must be described by a string;" " got %s" % type(guest) } assert isinstance(allocid, str) allocdb = get_from_cache(allocid) guestid = commonl.mkid(guest, l = 4) if calling_user.get_id() != guest \ and not allocdb.check_user_is_creator_admin(calling_user): return { "_message": "not allowed to remove guests from allocation" } allocdb.guest_remove(guest) return {}
def __init__(self, path="/usr/sbin/dnsmasq"): cmdline = [ path, "--keep-in-foreground", "--pid-file=%(path)s/dnsmasq.pid", "--conf-file=%(path)s/dnsmasq.conf", ] ttbl.power.daemon_c.__init__(self, cmdline, precheck_wait=0.5, mkpidfile=False, pidfile="%(path)s/dnsmasq.pid") self.upid_set( "dnsmasq daemon", # if multiple virtual machines are created associated to a # target, this shall generate a different ID for # each...in most cases serial_number=commonl.mkid(" ".join(cmdline)))
def _id_maybe_encode_cache(self, _tls, _made_in_pid, identifier, max_len): """ If an identifier is longer than the maximum, convert it and register it. Register it in the *Field IDs* table so we can later refer to it as needed. :param str identifier: identifier to check and maybe convert :return str: identifier if (shorter than :data:`id_max_len`) or the encoded name if it was longer. """ if len(identifier) >= max_len: fieldid = commonl.mkid(identifier, 10) self.table_row_update("Field IDs", "FieldID", fieldid, **{"Field Name": identifier}) return "fieldid:" + fieldid return self._sql_id_esc(identifier)
def __init__(self, name, cmdline, mimetype, extension="", pre_commands=None, wait_to_kill=2, use_signal=signal.SIGINT, kws=None): assert isinstance(name, str_type) assert wait_to_kill > 0 self.name = name if isinstance(cmdline, str): self.cmdline = cmdline.split() cmdline_s = cmdline else: commonl.assert_list_of_strings(cmdline, "commandline", "commands") self.cmdline = cmdline cmdline_s = ''.join(cmdline) self.wait_to_kill = wait_to_kill self.extension = extension self.use_signal = use_signal if pre_commands: self.pre_commands = pre_commands assert all([ isinstance(command, str_type) for command in pre_commands ]), \ "list of pre_commands have to be strings" else: self.pre_commands = [] impl_c.__init__(self, False, mimetype, log='text/plain') # we make the cmdline be the unique physical identifier, since # it is like a different implementation each self.upid_set(name, serial_number=commonl.mkid(cmdline_s)) if kws == None: self.kws = {} else: commonl.assert_dict_key_strings(kws, "key") self.kws = dict(kws)
def __init__(self, name, cmdline, mimetype, pre_commands=None, extension=""): assert isinstance(name, str_type) assert isinstance(cmdline, str_type) assert isinstance(extension, str_type) self.name = name self.cmdline = cmdline.split() if pre_commands: self.pre_commands = pre_commands assert all([ isinstance(command, str_type) for command in pre_commands ]), \ "list of pre_commands have to be strings" else: self.pre_commands = [] self.extension = extension impl_c.__init__(self, True, mimetype, log="text/plain") # we make the cmdline be the unique physical identifier, since # it is like a different implementation each self.upid_set(name, serial_number=commonl.mkid(cmdline))
def __init__(self, name, cmdline, mimetype, pre_commands=None, wait_to_kill=1): assert isinstance(name, basestring) assert isinstance(cmdline, basestring) assert wait_to_kill > 0 self.name = name self.cmdline = cmdline.split() self.wait_to_kill = wait_to_kill if pre_commands: self.pre_commands = pre_commands assert all([ isinstance(command, basestring) for command in pre_commands ]), \ "list of pre_commands have to be strings" else: self.pre_commands = [] impl_c.__init__(self, True, mimetype) # we make the cmdline be the unique physical identifier, since # it is like a different implementation each self.upid_set(name, serial_number=commonl.mkid(cmdline))
def check_userid_is_guest(self, userid): assert isinstance(userid, str) guestid = commonl.mkid(userid, l = 4) if self.get("guest." + guestid) == userid: return True return False
def __init__(self, config_text=None, config_files=None, use_ssl=False, tmpdir=None, keep_temp=True, errors_ignore=None, warnings_ignore=None, aka=None, local_auth=True): # Force all assertions, when running like this, to fail the TC tcfl.tc.tc_c.exception_to_result[AssertionError] = tcfl.tc.failed_e # If no aka is defined, we make one out of the place when this # object is being created, so it is always the same *and* thus # the report hashes are always identical with each run if aka == None: self.aka = "ttbd-" + commonl.mkid(commonl.origin_get(2), 4) else: self.aka = aka if config_files == None: config_files = [] self.keep_temp = keep_temp self.port = commonl.tcp_port_assigner() self.use_ssl = use_ssl if use_ssl == True: self.url = "https://localhost:%d" % self.port ssl_context = "" else: self.url = "http://localhost:%d" % self.port ssl_context = "--no-ssl" self.url_spec = "fullid:'^%s'" % self.aka if tmpdir: self.tmpdir = tmpdir else: # default to place the server's dir in the tempdir for # testcases self.tmpdir = os.path.join(tcfl.tc.tc_c.tmpdir, "server", self.aka) shutil.rmtree(self.tmpdir, ignore_errors=True) commonl.makedirs_p(self.tmpdir) self.etc_dir = os.path.join(self.tmpdir, "etc") self.files_dir = os.path.join(self.tmpdir, "files") self.lib_dir = os.path.join(self.tmpdir, "lib") self.state_dir = os.path.join(self.tmpdir, "state") os.mkdir(self.etc_dir) os.mkdir(self.files_dir) os.mkdir(self.lib_dir) os.mkdir(self.state_dir) self.stdout = self.tmpdir + "/stdout" self.stderr = self.tmpdir + "/stderr" for fn in config_files: shutil.copy(fn, self.etc_dir) with open(os.path.join(self.etc_dir, "conf_00_base.py"), "w") as cfgf: cfgf.write(r""" import ttbl.config ttbl.config.processes = 2 host = '127.0.0.1' """) # We don't define here the port, so we see it in the # command line if config_text: cfgf.write(config_text) self.srcdir = os.path.realpath( os.path.join(os.path.dirname(__file__), "..")) self.cmdline = [ "stdbuf", "-o0", "-e0", # This allows us to default to the source location,when # running from source, or the installed when running from # the system os.environ.get("TTBD_PATH", self.srcdir + "/ttbd/ttbd"), "--port", "%d" % self.port, ssl_context, "-vvvvv", "--files-path", self.files_dir, "--state-path", self.state_dir, "--config-path", "", # This empty one is to clear them all "--config-path", self.etc_dir ] self.local_auth = local_auth if local_auth: self.cmdline.append("--local-auth") self.p = None #: Exclude these regexes / strings from triggering an error #: message check self.errors_ignore = [] if errors_ignore == None else errors_ignore #: Exclude these regexes / strings from triggering an warning #: message check self.warnings_ignore = [re.compile('daemon lacks CAP_NET_ADMIN')] if warnings_ignore: self.warnings_ignore += warnings_ignore def _preexec_fn(): stdout_fd = os.open( self.stdout, # O_CREAT: Always a new file, so # we can check for errors and not # get confused with previous runs os.O_WRONLY | os.O_EXCL | os.O_CREAT, 0o0644) stderr_fd = os.open( self.stderr, # O_CREAT: Always a new file, so # we can check for errors and not # get confused with previous runs os.O_WRONLY | os.O_EXCL | os.O_CREAT, 0o0644) os.dup2(stdout_fd, 1) os.dup2(stderr_fd, 2) logging.info("Launching: %s", " ".join(self.cmdline)) self.p = subprocess.Popen(self.cmdline, shell=False, cwd=self.tmpdir, close_fds=True, preexec_fn=_preexec_fn, bufsize=0) try: self._check_if_alive() finally: self.check_log_for_issues() # if we call self.terminate() from __del__, the garbage # collector has started to wipe things, so we can't use, ie: # open() to check the log file atexit.register(self.terminate)
def _target_setup(self, target): self.instrumentation_publish_component( target, "fastboot", commonl.mkid(self.usb_serial_number, l = 4), "ADB bridge", { 'usb_serial_number': self.usb_serial_number })
def __init__(self, config_text = None, config_files = None, use_ssl = False, tmpdir = None, keep_temp = True, errors_ignore = None, warnings_ignore = None, aka = None): # Force all assertions, when running like this, to fail the TC tcfl.tc.tc_c.exception_to_result[AssertionError] = tcfl.tc.failed_e # If no aka is defined, we make one out of the place when this # object is being created, so it is always the same *and* thus # the report hashes are always identical with each run if aka == None: self.aka = "ttbd-" + commonl.mkid(commonl.origin_get(2), 4) else: self.aka = aka if config_files == None: config_files = [] self.keep_temp = keep_temp self.port = commonl.tcp_port_assigner() self.use_ssl = use_ssl if use_ssl == True: self.url = "https://localhost:%d" % self.port ssl_context = "" else: self.url = "http://localhost:%d" % self.port ssl_context = "--no-ssl" self.url_spec = "url:'^%s'" % self.url if tmpdir: self.tmpdir = tmpdir else: # Don't use colon on the name, or it'll thing it is a path self.tmpdir = tempfile.mkdtemp(prefix = "test-ttbd-%d." % self.port) self.etc_dir = os.path.join(self.tmpdir, "etc") self.files_dir = os.path.join(self.tmpdir, "files") self.lib_dir = os.path.join(self.tmpdir, "lib") self.state_dir = os.path.join(self.tmpdir, "state") os.mkdir(self.etc_dir) os.mkdir(self.files_dir) os.mkdir(self.lib_dir) os.mkdir(self.state_dir) self.stdout = self.tmpdir + "/stdout" self.stderr = self.tmpdir + "/stderr" for fn in config_files: shutil.copy(fn, self.etc_dir) with open(os.path.join(self.etc_dir, "conf_00_base.py"), "w") as cfgf: cfgf.write(r""" import ttbl.config ttbl.config.processes = 2 host = '127.0.0.1' """) # We don't define here the port, so we see it in the # command line if config_text: cfgf.write(config_text) srcdir = os.path.realpath( os.path.join(os.path.dirname(__file__), "..")) self.cmdline = [ # This allows us to default to the source location,when # running from source, or the installed when running from # the system os.environ.get("TTBD_PATH", srcdir + "/ttbd/ttbd"), "--port", "%d" % self.port, ssl_context, "--local-auth", "-vvvvv", "--files-path", self.files_dir, "--state-path", self.state_dir, "--var-lib-path", self.lib_dir, "--config-path", "", # This empty one is to clear them all "--config-path", self.etc_dir ] self.p = None #: Exclude these regexes / strings from triggering an error #: message check self.errors_ignore = [] if errors_ignore == None else errors_ignore #: Exclude these regexes / strings from triggering an warning #: message check self.warnings_ignore = [ re.compile('daemon lacks CAP_NET_ADMIN') ] if warnings_ignore: self.warnings_ignore =+ warnings_ignore def _preexec_fn(): stdout_fd = os.open(self.stdout, # O_CREAT: Always a new file, so # we can check for errors and not # get confused with previous runs os.O_WRONLY | os.O_EXCL |os.O_CREAT, 0o0644) stderr_fd = os.open(self.stderr, # O_CREAT: Always a new file, so # we can check for errors and not # get confused with previous runs os.O_WRONLY | os.O_EXCL |os.O_CREAT, 0o0644) os.dup2(stdout_fd, 1) os.dup2(stderr_fd, 2) logging.info("Launching: %s", " ".join(self.cmdline)) self.p = subprocess.Popen( self.cmdline, shell = False, cwd = self.tmpdir, close_fds = True, preexec_fn = _preexec_fn) self._check_if_alive() self.check_log_for_issues()
def _target_setup(self, target, iface_name): instr_id = commonl.mkid(self.usb_serial_number, l=4) self.instrumentation_publish_component( target, "fastboot", instr_id, "ADB bridge", {'usb_serial_number': self.usb_serial_number}) target.tags['interfaces'][iface_name]['instrument'] = instr_id
def _target_setup(self, target): self.instrumentation_publish_component( target, "ioc_flash_server_app", commonl.mkid(self.tty_path, l=4), "RS-232C serial port", {'serial_port': self.tty_path})
def _target_setup(self, target, iface_name): instr_id = commonl.mkid(self.tty_path, l=4) self.instrumentation_publish_component(target, "ioc_flash_server_app", instr_id, "RS-232C serial port", {'serial_port': self.tty_path}) target.tags['interfaces'][iface_name]['instrument'] = instr_id
def guest_remove(self, userid): # a guest is trying to delete, which just removes the user guestid = commonl.mkid(userid, l = 4) # same as guest_remove() self.set("guest." + guestid, None)
def create_filename(userid): """ Makes a safe filename based on the user ID """ filename = "_user_" + commonl.mkid(userid) return os.path.join(User.state_dir, filename)
def boot_network_http(target, entry, url, assume_in_main_menu = False): """ From the main menu, select an HTTP boot entry and boot it; if missing, add it go to the boot menu and boot an entry :param tcfl.tc.target_c target: target on which to operate (uses the default console) :param str entry: entry name; field *%(ID)s* will be replaced with a hash of *url*; if not found, it will be created as an HTTP boot entry with that name. :param bool assume_in_main_menu: (optional, default *False*) assume the BIOS is already in the main menu, otherwise wait for it to arrive. :returns bool: *True* if the entry was found and selected, *False* if not """ # FIXME: do straight from the boot menu with the hot key assert isinstance(target, tcfl.tc.target_c) assert isinstance(entry, basestring) assert isinstance(url, basestring) assert isinstance(assume_in_main_menu, bool) top = 4 kws = dict(ID = commonl.mkid(url, l = 4)) entry = entry % kws for cnt in range(top): if assume_in_main_menu: assume_in_main_menu = False else: main_menu_expect(target) if main_boot_select_entry(target, entry): entry_select(target) # select it break target.report_info("BIOS: can't find HTTP network boot entry '%s';" " attempting to enable EFI network support" % entry) # Ensure EFI networking is on menu_escape_to_main(target) if menu_config_network_enable(target): # EFI Networking was disabled, now it is enabled. we need to # reset before we can see the network device list (once we # have enabled networking) menu_escape_to_main(target, esc_first = False) menu_reset(target) main_menu_expect(target) else: menu_escape_to_main(target, esc_first = False) # Now go to create our HTTP boot entry r = menu_dig_to( target, # FIXME: make this path object's property [ "EDKII Menu", "Network Device List", # seriusly..the entry is called MAC: but the menu # title "Network Device MAC:"... ( "MAC:(?P<macaddr>[A-F0-9:]+)", "Network Device MAC:(?P<macaddr>[A-F0-9:]+)" ), "HTTP Boot Configuration", ], # FIXME: make this default canary_end_menu_redrawn = "Esc=Exit") if not r: raise tcfl.tc.error_e( "BIOS: can't get to the menu to add HTTP boot; giving up") boot_network_http_boot_add_entry(target, entry, url) # fall through, try again menu_escape_to_main(target, esc_first = False) assume_in_main_menu = True else: raise tcfl.tc.error_e( "BIOS: HTTP network boot failed %s/%s; giving up" % (cnt, top))