def initialize_lxd(): if is_initialized(): return try: with open(os.path.join(config.provision_dir, "lxd-init.yaml"), "r") as f: init = f.read() except OSError as e: raise LXCException(f"Error reading lxd-init.yaml {e}") try: logging.info("Updating package information...") vm.run_cmd("sudo apt update", show_spinner=True) vm.run_cmd("sudo usermod yurt -a -G lxd") logging.info("Initializing LXD...") vm.run_cmd( "sudo lxd init --preseed", stdin=init, show_spinner=True ) _setup_yurt_socat() logging.info("Done.") config.set_config(config.Key.is_lxd_initialized, True) except VMException as e: logging.error(e) logging.error("Restart the VM to try again: 'yurt vm restart'") raise LXCException("Failed to initialize LXD.")
def get_instance(name: str): client = get_pylxd_client() try: return client.instances.get(name) # pylint: disable=no-member except pylxd.exceptions.NotFound: raise LXCException(f"Instance {name} not found.") except pylxd.exceptions.LXDAPIException: raise LXCException( f"Could not fetch instance {name}. API Error.")
def get_lxc_executable(): if config.system == config.System.windows: lxc_executable = os.path.join(config.bin_dir, "lxc.exe") if os.path.isfile(lxc_executable): return lxc_executable else: raise LXCException( f"{lxc_executable} does not exist.") else: raise LXCException( f"LXC executable not found for platform: {config.system}")
def delete(names: List[str]): for name in names: instance = util.get_instance(name) try: instance.delete(wait=True) except LXDAPIException as e: raise LXCException(f"Error deleting instance: {e}")
def list_remote_images(remote: str): from functools import partial import json try: # We'd have to implement simplestreams ourselves as this call is handled # entirely by the client. Let's cheat. output, error = vm.run_cmd(f"lxc image list {remote}: --format json", show_spinner=True) if error: logging.error(error) images = util.filter_remote_images(json.loads(output)) images_info = filter( None, map(partial(util.get_remote_image_info, remote), images)) if remote == "ubuntu": return sorted(images_info, key=lambda i: i["Alias"], reverse=True) else: return sorted(images_info, key=lambda i: i["Alias"]) except VMException as e: message = f"Could not fetch remote images: {e.message}" logging.error("Please confirm that you're connected to the internet.") raise LXCException(message)
def list_(): import json def get_info(instance): try: addresses = instance["state"]["network"]["eth0"]["addresses"] ipv4_info = find(lambda a: a["family"] == "inet", addresses, {}) ipv4_address = ipv4_info.get("address", "") except KeyError as e: logging.debug(f"Key Error: {e}") ipv4_address = "" except TypeError: ipv4_address = "" instance_config = instance["config"] architecture = instance_config.get("image.architecture", "") os_ = instance_config.get("image.os", "") release = instance_config.get("image.release", "") return { "Name": instance["name"], "Status": instance["state"]["status"], "IP Address": ipv4_address, "Image": f"{os_}/{release} ({architecture})" } try: output = run_lxc(["list", "--format", "json"], show_spinner=True) instances = json.loads(output) return list(map(get_info, instances)) except CommandException as e: raise LXCException(f"Failed to list networks: {e.message}")
def get_pylxd_client(): lxd_port = config.get_config(config.Key.lxd_port) try: return pylxd.Client(endpoint=f"http://127.0.0.1:{lxd_port}") except pylxd.exceptions.ClientConnectionFailed as e: logging.debug(e) raise LXCException( "Error connecting to LXD. Try restarting the VM: 'yurt vm restart'")
def list_cached_images(): try: output = run_lxc(["image", "list", "yurt:", "--format", "json"], show_spinner=True) images_info = filter(None, map(get_cached_image_info, json.loads(output))) return list(images_info) except CommandException as e: raise LXCException(f"Could not fetch images - {e.message}")
def exec_interactive(instance_name: str, cmd: List[str], environment=None): from . import term instance = get_instance(instance_name) response = instance.raw_interactive_execute(cmd, environment=environment) lxd_port = config.get_config(config.Key.lxd_port) try: ws_url = f"ws://127.0.0.1:{lxd_port}{response['ws']}" term.run(ws_url) except KeyError as e: raise LXCException(f"Missing ws URL {e}")
def launch(remote: str, image: str, name: str): # https://linuxcontainers.org/lxd/docs/master/instances # Valid instance names must: # - Be between 1 and 63 characters long # - Be made up exclusively of letters, numbers and dashes from the ASCII table # - Not start with a digit or a dash # - Not end with a dash client = util.get_pylxd_client() try: server_url = util.REMOTES[remote]["URL"] except KeyError: raise LXCException(f"Unsupported remote {remote}") try: logging.info( f"Launching container '{name}'. This might take a few minutes...") response = client.api.instances.post( json={ "name": name, "profiles": [util.PROFILE_NAME], "source": { "type": "image", "alias": image, "mode": "pull", "server": server_url, "protocol": "simplestreams" } }) util.follow_operation( response.json()["operation"], unpack_metadata=util.unpack_download_operation_metadata) logging.info(f"Starting container") instance = util.get_instance(name) instance.start(wait=True) except LXDAPIException as e: logging.error(e) raise LXCException(f"Failed to launch instance {name}")
def list_remote_images(remote: str): from functools import partial try: output = run_lxc(["image", "list", f"{remote}:", "--format", "json"], show_spinner=True) images = filter_remote_images(json.loads(output)) images_info = filter( None, map(partial(get_remote_image_info, remote), images)) if remote == "ubuntu": return sorted(images_info, key=lambda i: i["Alias"], reverse=True) else: return sorted(images_info, key=lambda i: i["Alias"]) except CommandException as e: raise LXCException(f"Could not fetch images: {e.message}")
def get_cached_image_info(image: Dict): try: alias = image["update_source"]["alias"] server = image["update_source"]["server"] remote = find(lambda r: r["URL"] == server, REMOTES, None) if remote: source = f"{remote['Name']}:{alias}" else: raise LXCException("Unexpected source server: {}") return { "Alias": source, "Description": image["properties"]["description"] } except KeyError as e: logging.debug(e) logging.debug(f"Unexpected image schema: {image}")
def get_ip_config(): from ipaddress import ip_interface host_ip_address = config.get_config( config.Key.interface_ip_address) network_mask = config.get_config( config.Key.interface_netmask) if not (host_ip_address and network_mask): raise LXCException("Bad IP Configuration. ip: {0}, mask: {1}".format( host_ip_address, network_mask)) full_host_address = ip_interface( "{0}/{1}".format(host_ip_address, network_mask)) bridge_address = ip_interface( "{0}/{1}".format((full_host_address + 1).ip, network_mask)).exploded return { "bridgeAddress": bridge_address, "dhcpRangeLow": (full_host_address + 10).ip.exploded, "dhcpRangeHigh": (full_host_address + 249).ip.exploded }
def initialize_lxd(): lxd_init = """config: core.https_address: '[::]:8443' core.trust_password: yurtsecret networks: [] storage_pools: - config: source: /dev/sdc description: "" name: yurtpool driver: zfs profiles: - config: {} description: "" devices: root: path: / pool: yurtpool type: disk name: default cluster: null """ try: logging.info("Installing LXD. This might take a few minutes...") run_in_vm("sudo snap install lxd", show_spinner=True) logging.info("LXD installed. Configuring...") run_in_vm("sudo lxd.migrate -yes", show_spinner=True) run_in_vm( "sudo lxd init --preseed", stdin=lxd_init, show_spinner=True ) logging.info("Done.") config.set_config(config.Key.is_lxd_initialized, True) except RemoteCommandException as e: logging.error(e) logging.info("Restart the VM to try again: 'yurt shutdown; yurt boot'") raise LXCException("Failed to initialize LXD.")
def follow_operation(operation_uri: str, unpack_metadata=None): """ Params: operation_uri: URI of the operation to follow. unpack_metadata: Function to unpack the operation's metadata. Return a line of text to summarize the current progress of the operation. If not given, progress will not be shown. """ import time from yurt.util import retry operations = get_pylxd_client().operations # Allow time for operation to be created. try: retry( lambda: operations.get(operation_uri), # pylint: disable=no-member retries=10, wait_time=0.5 ) operation = operations.get(operation_uri) # pylint: disable=no-member except pylxd.exceptions.NotFound: raise LXCException( f"Timed out while waiting for operation to be created.") logging.info(operation.description) while True: try: operation = operations.get( # pylint: disable=no-member operation_uri ) if unpack_metadata: print(f"\r{unpack_metadata(operation.metadata)}", end="") time.sleep(0.5) except pylxd.exceptions.NotFound: print("\nDone") break except KeyboardInterrupt: break