def run(self, target=None, initrd=None, background=False, # noqa: C901 paused=False, gdb=4123, dbg=False, virtio_nic=None, bridge=None, interface=None, dry_run=False, args=None, memory=64, cpu_sockets=1, cpu_cores=1): if target is None: raise KraftError('Target not set') elif target.binary is None: raise KraftError('Target has not been compiled') elif not os.path.exists(target.binary): raise KraftError('Could not find unikernel: %s' % target.binary) logger.debug("Running binary: %s" % target.binary) runner = target.platform.runner runner.use_debug = dbg runner.architecture = target.architecture.name if initrd: runner.add_initrd(initrd) if virtio_nic: runner.add_virtio_nic(virtio_nic) if bridge: runner.add_bridge(bridge) if interface: runner.add_interface(interface) if gdb: runner.open_gdb(gdb) if memory: runner.set_memory(memory) if cpu_sockets: runner.set_cpu_sockets(cpu_sockets) if cpu_cores: runner.set_cpu_cores(cpu_cores) runner.unikernel = target.binary runner.execute( extra_args=args, background=background, paused=paused, dry_run=dry_run, )
def validate_targets_section(config_file, config): if not isinstance(config, list): raise KraftError( "Top level object in '{}' needs to be an object not '{}'.".format( config_file.filename, type(config))) return config
def validate_top_level_string_or_list(config_file, config, section): if not isinstance(config, (six.string_types, list)) or config is None: raise KraftError( "Top level object in '{}' needs to be a string or array not '{}'." .format(config_file.filename, type(config))) return config
def validate_unikraft_section(config_file, config): if not isinstance(config, (six.string_types, dict, int, float)): raise KraftError( "Top level object in '{}' needs to be an object not '{}'.".format( config_file.filename, type(config))) return config
def validate_top_level_string(config_file, config, section): if not isinstance(config, six.string_types): raise KraftError( "Top level object in '{}' needs to be a string not '{}'.".format( config_file.filename, type(config))) return config
def load_jsonschema(config_file): filename = os.path.join( get_schema_path(), "specification_v{0}.json".format(config_file.version)) if not os.path.exists(filename): raise KraftError('Specification in "{}" is unsupported. {}'.format( filename, SPECIFICATION_EXPLANATION)) with open(filename, "r") as fh: return json.load(fh)
def version(self): if 'specification' not in self.config: return KRAFT_SPEC_LATEST version = str(self.config['specification']) version_pattern = re.compile(r"^[0-9]+(\.\d+)?$") if not version_pattern.match(version): raise KraftError('Specification "{}" in "{}" is invalid.'.format( version, self.filename)) return SpecificationVersion(version)
def load_yaml(filename, encoding=None, binary=True): try: with io.open(filename, 'rb' if binary else 'r', encoding=encoding) as fh: return yaml.safe_load(fh) except (IOError, yaml.YAMLError, UnicodeDecodeError) as e: if encoding is None: # Sometimes the user's locale sets an encoding that doesn't match # the YAML files. Im such cases, retry once with the "default" # UTF-8 encoding return load_yaml(filename, encoding='utf-8-sig', binary=False) error_name = getattr(e, '__module__', '') + '.' + e.__class__.__name__ raise KraftError(u"{}: {}".format(error_name, e))
def generate_bridge_name(self, prefix='virbr', max_tries=1024): suffix_i = 0 new_name = None while suffix_i < max_tries: new_name = prefix + str(suffix_i) if not self.bridge_exists(new_name): return new_name suffix_i += 1 raise KraftError("Max tries for bridge creation reached!")
def handle_errors(errors, format_error_func, filename): """jsonschema returns an error tree full of information to explain what has gone wrong. Process each error and pull out relevant information and re-write helpful error messages that are relevant. """ errors = list(sorted(errors, key=str)) if not errors: return error_msg = '\n'.join(format_error_func(error) for error in errors) raise KraftError( "The Kraft file{file_msg} is invalid because:\n{error_msg}".format( file_msg=" '{}'".format(filename) if filename else "", error_msg=error_msg))
def validate_component_section(filename, config, section): """Validate the structure of a configuration section. This must be done before interpolation so it's separate from schema validation. """ if not isinstance(config, dict): raise KraftError( "In file '{filename}', {section} must be a mapping, not " "{type}.".format(filename=filename, section=section, type=anglicize_json_type( python_type_to_yaml_type(config)))) for key, value in config.items(): if not isinstance(key, six.string_types): raise KraftError( "In file '{filename}', the {section} name {name} must be a " "quoted string, i.e. '{name}'.".format(filename=filename, section=section, name=key)) # Turn a None type into a boolean, so that listing it returns as True if value is None: value = True config[key] = True if not isinstance(value, (six.string_types, dict, bool, int, float, list)): raise KraftError( "In file '{filename}', {section} '{name}' must be a mapping not " "{type}.".format(filename=filename, section=section, name=key, type=anglicize_json_type( python_type_to_yaml_type(value)))) return config
def kraft_configure(ctx, env=None, workdir=None, target=None, plat=None, arch=None, force_configure=False, show_menuconfig=False, options=[], use_versions=[]): """ Populates the local .config with the default values for the target application. """ if workdir is None or os.path.exists(workdir) is False: raise ValueError("working directory is empty: %s" % workdir) logger.debug("Configuring %s..." % workdir) app = Application.from_workdir( workdir=workdir, force_init=force_configure, use_versions=use_versions, ) if show_menuconfig: if sys.stdout.isatty(): app.open_menuconfig() return else: raise KraftError("Cannot open menuconfig in non-TTY environment") if app.is_configured() and force_configure is False: if click.confirm( "%s is already configured, would you like to overwrite configuration?" % workdir): # noqa force_configure = True else: raise CannotConfigureApplication(workdir) if len(app.config.targets.all()) == 1: target = app.config.targets.all()[0] elif len(app.binaries) == 1: target = app.binaries[0] else: for t in app.config.targets.all(): # Did the user specific a target-name? if target is not None and target == t.name: target = t break # Did the user specify arch AND plat combo? Does it exist? elif arch == t.architecture.name \ and plat == t.platform.name: target = t break # The user did not specify something if target is None: binaries = [] for t in app.binaries: binname = os.path.basename(t.binary) if t.name is not None: binname = "%s (%s)" % (binname, t.name) binaries.append(binname) # Prompt user for binary selection answers = inquirer.prompt([ inquirer.List( 'target', message="Which target would you like to configure?", choices=binaries, ), ]) # Work backwards from binary name for t in app.binaries: if answers['target'] == os.path.basename(t.binary): target = t break app.configure( target=target, options=options, force_configure=force_configure, ) app.save_yaml()
def run( self, target=None, initrd=None, background=False, # noqa: C901 paused=False, gdb=4123, dbg=False, virtio_nic=None, bridge=None, interface=None, dry_run=False, args=None, memory=64, cpu_sockets=1, cpu_cores=1): if target is None: raise KraftError('Target not set') elif target.binary is None: raise KraftError('Target has not been compiled') elif not os.path.exists(target.binary): raise KraftError('Could not find unikernel: %s' % target.binary) logger.debug("Running binary: %s" % target.binary) runner = target.platform.runner runner.use_debug = dbg runner.architecture = target.architecture.name if initrd: runner.add_initrd(initrd) if virtio_nic: runner.add_virtio_nic(virtio_nic) if bridge: runner.add_bridge(bridge) if interface: runner.add_interface(interface) if gdb: runner.open_gdb(gdb) if isinstance(memory, int) and memory > 0: runner.set_memory(memory) if cpu_sockets: runner.set_cpu_sockets(cpu_sockets) if cpu_cores: runner.set_cpu_cores(cpu_cores) for volume in self.config.volumes.all(): if volume.driver is VolumeDriver.VOL_9PFS: path = os.path.join(self.localdir, volume.name) if not os.path.exists(path): tar = tarfile.open(volume.source) tar.extractall(path) tar.close() runner.add_virtio_9pfs(path) runner.unikernel = target.binary runner.execute( extra_args=args, background=background, paused=paused, dry_run=dry_run, )
def configure(ctx, self, target=None, arch=None, plat=None, options=[], force_configure=False): """ Configure a Unikraft application. """ if not self.is_configured(): self.init() if target is not None and isinstance(target, Target): arch = target.architecture plat = target.platform archs = list() plats = list() def match_arch(arch, target): if isinstance(arch, six.string_types) and \ arch == target.architecture.name: return target.architecture if isinstance(arch, Architecture) and \ arch.name == target.architecture.name: return arch return None def match_plat(plat, target): if isinstance(plat, six.string_types) and \ plat == target.platform.name: return target.platform if isinstance(plat, Platform) and \ plat.name == target.platform.name: return plat return None if len(self.config.targets.all()) == 1 \ and target is None and arch is None and plat is None: target = self.config.targets.all()[0] archs.append(target.architecture) plats.append(target.platform) else: for t in self.config.targets.all(): if match_arch(arch, t) is not None \ and match_plat(plat, t) is not None: archs.append(t.architecture) plats.append(t.platform) # Generate a dynamic .config to populate defconfig with based on # configure's parameterization. dotconfig = list() dotconfig.extend(self.config.unikraft.kconfig or []) for arch in archs: if not arch.is_downloaded: raise MissingComponent(arch) dotconfig.extend(arch.kconfig) dotconfig.append(arch.kconfig_enabled_flag) for plat in plats: if not plat.is_downloaded: raise MissingComponent(plat) dotconfig.extend(plat.kconfig) dotconfig.append(plat.kconfig_enabled_flag) for lib in self.config.libraries.all(): if not lib.is_downloaded: raise MissingComponent(lib) dotconfig.extend(lib.kconfig) dotconfig.append(lib.kconfig_enabled_flag) # Add any additional confguration options, and overriding existing # configuraton options. for new_opt in options: o = new_opt.split('=') for exist_opt in dotconfig: e = exist_opt.split('=') if o[0] == e[0]: dotconfig.remove(exist_opt) break dotconfig.append(new_opt) # Create a temporary file with the kconfig written to it fd, path = tempfile.mkstemp() with os.fdopen(fd, 'w+') as tmp: logger.debug('Using the following defconfig:') for line in dotconfig: logger.debug(' > ' + line) tmp.write(line + '\n') return_code = 0 try: return_code = self.make([('UK_DEFCONFIG=%s' % path), 'defconfig']) finally: os.remove(path) if return_code > 0: raise KraftError("Could not configure application") return True