Example #1
0
def create_lxc_storage_pool(name=settings.LXC_STORAGE_POOL_NAME,
                            driver=settings.LXC_STORAGE_POOL_DRIVER):
    """
    Creates a new LXC storage pool with the passed driver and name
    :param str name: The name of the storage pool to create (default from settings)
    :param str driver: The driver to use (default from settings)
    """
    # Sanity check, we don't want to create duplicate pools
    if check_if_lxc_storage_pool_exists(name):
        raise RuntimeError(
            "LXC storage pool {} already exists, cannot create duplicate".
            format(name))

    # Make it
    logger.info("Creating LXC storage pool {} with driver {}".format(
        name, driver))
    client = get_lxd_client()
    try:
        client.storage_pools.create({
            "name": name,
            "driver": driver,
            "config": {
                "size": settings.LXC_STORAGE_POOL_SIZE
            }
        })
        logger.info(
            "Storage pool {} with driver {} successfully created".format(
                name, driver))
    except LXDAPIException as e:
        logger.critical(
            "Got API error while creating storage pool {}. Error: {}".format(
                name, e))
        raise RuntimeError(
            "Received API error while creating storage pool {}".format(name))
Example #2
0
def check_if_lxc_machine_exists(machine):
    """
    Checks if an LXC machine exists
    :param str machine: The machine/container to check for
    :return: bool: True if it exists, false otherwise
    """
    return get_lxd_client().containers.exists(machine)
Example #3
0
def check_if_lxc_storage_pool_exists(name=settings.LXC_STORAGE_POOL_NAME):
    """
    Check for the existence of the LXC storage pool defined in the settings
    :param str name: The name of the storage pool to check for (default from the settings)
    :return: bool: True if the pool exists, False otherwise
    """
    logger.debug(
        "Checking for the existence of LXC {} storage pool".format(name))
    return get_lxd_client().storage_pools.exists(
        settings.LXC_STORAGE_POOL_NAME)
Example #4
0
def write_file_to_lxc_container(container, file_path, data):
    """
    Writes file data to a path on the LXC container
    :param container: str: The name of the container
    :param file_path: str: The guest path to write the file to
    :param data: The data to write
    """
    try:
        machine = get_lxd_client().containers.get(container)
        machine.files.put(file_path, data)
    except NotFound:
        logger.error("Tried to write data to path {} on LXC container {}, but the container does not exist".format(file_path, container))
Example #5
0
def get_lxc_machine_status(name):
    """
    Gets the LXC machine state and returns a list
    :param name: str: The name of the machine
    :return: list: [name, state, provider]
    """
    # TODO: Let's not return a list here, simply the status
    client = get_lxd_client()
    try:
        status = client.containers.get(name).status
    except NotFound:
        status = "NA"
    return [name, status, "LXC"]
Example #6
0
def create_lxc_base_image_container(config):
    """
    Creates the LXC base image container
    :param dict config: The config generated by get_config()
    :raises RuntimeError: if any issues are encountered during creation
    """
    # Check if the base image machine already exists and destroy it
    if check_if_lxc_machine_exists(settings.LXC_BASE_IMAGE_MACHINE_NAME):
        logger.warning("LXC base image machine already exists")
        request_confirmation(
            message="Recreating it will destroy any local changes",
            prompt="Recreate the LXC base image machine? ")
        destroy_lxc_machine(settings.LXC_BASE_IMAGE_MACHINE_NAME, wait=True)

    # Now create the base image machine
    client = get_lxd_client()

    machine_config = {
        "name": settings.LXC_BASE_IMAGE_MACHINE_NAME,
        "architecture": "x86_64",
        "profiles": [settings.LXC_VNET_PROFILE],
        "ephemeral": False,
        "config": {},
        "devices": {
            "eth0": {
                "name":
                "eth0",
                "parent":
                "lxdbr0",
                "type":
                "nic",
                "nictype":
                "bridged",
                "host_name":
                "{}-eth0".format(settings.LXC_BASE_IMAGE_MACHINE_NAME),
            }
        },
        "source": {
            "type": "image",
            "protocol":
            str(config["providers"]["lxc"]["base_image"]["protocol"]),
            "server": config["providers"]["lxc"]["base_image"]["server"],
            "alias": str(config["providers"]["lxc"]["base_image"]["os"]),
        },
    }
    logger.info("Creating LXC base image container")
    client.containers.create(machine_config, wait=True)
Example #7
0
def check_if_lxc_image_exists(image, by_alias=True):
    """
    Check if an LXC image exists
    :param str image: The image fingerprint or alias
    :param bool by_alias: Search by image alias instead of fingerprint
    :return: bool: True if the image exists false otherwise
    """
    logger.debug("Checking for the existence of LXC image {}".format(image))
    client = get_lxd_client()
    if by_alias:
        try:
            client.images.get_by_alias(image)
            return True
        except NotFound:
            return False
    # Search by fingerprint
    return client.images.exists(image)
Example #8
0
def destroy_lxc_image(image, by_alias=True):
    """
    Destroy a LXC image
    :param str image: The fingerprint or alias of the image to destroy
    :param bool by_alias: Search by alias instead of fingerprint
    """
    # Check if it even exists
    if not check_if_lxc_image_exists(image, by_alias=by_alias):
        logger.warning(
            "Tried to destroy LXC image {}, but it is already gone".format(
                image))
        return
    # Delete it
    logger.info("Deleting LXC image {}".format(image))
    client = get_lxd_client()
    image = client.images.get_by_alias(
        image) if by_alias else client.images.get(image)
    image.delete()
Example #9
0
def create_lxc_image_from_container(container, alias=None, description=None):
    """
    Create a LXC image from a container
    :param str container: The container to create the image from
    :param str alias: Creates an alias for the image
    :param str description: A description for the image alias
    """
    # Stop it first
    change_lxc_machine_status(container, status="stop")

    # Create the image
    logger.info("Creating image from LXC container {}".format(container))
    client = get_lxd_client()
    container = client.containers.get(container)
    img = container.publish(wait=True)
    logger.info("Image {} created successfully".format(img.fingerprint))

    # Create the alias if requested
    if alias:
        logger.info("Adding alias {} to newly created image".format(alias))
        img.add_alias(alias, description)
Example #10
0
def destroy_lxc_machine(machine, wait=False):
    """
    Deletes an LXC machine
    :param str machine: The name of the machine to delete
    :param bool wait: Wait for the deletion to be complete before returning
    :return:
    """
    client = get_lxd_client()
    try:
        container = client.containers.get(machine)
    except NotFound:
        logger.warning(
            "Tried to delete LXC machine {}, but it does not exist. Maybe it was already deleted?"
            .format(machine))
        return
    # Check if the container is still running
    if container.status.lower() == "running":
        logger.info("Stopping LXC container {}".format(machine))
        container.stop(wait=True)
    logger.info("Deleting LXC container {}".format(machine))
    container.delete(wait=wait)
Example #11
0
def change_lxc_machine_status(machine, status="stop"):
    """
    Start a LXC machine
    :param str machine: The name of the machine to change the status of
    :param str status: The status to change the LXC machine to
    """
    client = get_lxd_client()
    try:
        machine = client.containers.get(machine)
    except NotFound:
        logger.error(
            "Tried to change machine status of LXC container {}, but it doesn't exist!"
            .format(machine))
        return
    # Change the status
    if status == "stop":
        machine.stop()
    elif status == "start":
        try:
            # On start we wait, as we might catch invalid configs
            machine.start(wait=True)
        except LXDAPIException as e:
            logger.error(
                "Unable to start LXC container {}, got error: {}".format(
                    machine.name, e))
            return
    # Take a short nap after issuing the start/stop command, so we might pass the first status check
    sleep(1)
    try:
        required_state = "Stopped" if status == "stop" else "Running"
        wait_for_lxc_machine_status(machine, required_state)
        logger.debug("LXC container {} is {}".format(machine.name,
                                                     machine.state().status))
    except TimeoutError:
        logger.error(
            "Unable to change LXC status container {}, got timeout after issuing {} command"
            .format(machine.name, status))
Example #12
0
def delete_lxc_storage_pool(name):
    """
    Deletes a LXC storage pool
    Pool must be empty before deletion
    :param str name: The name of the pool to delete
    """
    # Check if the pool even exists
    if not check_if_lxc_storage_pool_exists(name):
        logger.warning(
            "Tried to delete LXC storage pool {}, but it didn't exist, skipping..."
            .format(name))
        return

    client = get_lxd_client()
    # Try to delete it
    try:
        logger.info("Deleting LXC storage pool {}".format(name))
        client.storage_pools.get(name).delete()
    except LXDAPIException as e:
        logger.critical(
            "Got API error while deleting storage pool {}. Error: {}".format(
                name, e))
        raise RuntimeError(
            "Received API error while deleting storage pool {}".format(name))
Example #13
0
def create_lxc_machines_from_base_image(config, containers):
    """
    Create LXC machines from the base image specified in the settings
    :param dict config: The config generated by get_config()
    :param list containers: A list of machines to create
    """
    containers_to_create = []
    containers_already_created = False

    # Get all the LXC machines to create
    for container in containers:
        # Check if the requested machine name is present in the config
        if container not in config["machines"]:
            logger.error(
                "Tried to get provider for container {}, but the container was not found in the config, skipping"
                .format(container))
        # Quick check if the machine already exists
        elif check_if_lxc_machine_exists(container):
            logger.error(
                "A LXC container with the name {} already exists, skipping".
                format(container))
            containers_already_created = True
        # Check if LXC is the provider
        elif settings.MACHINE_TYPE_PROVIDER_MAPPING[
                config["machines"][container]["type"]].lower() == "lxc":
            logger.debug(
                "Selecting LXC machine {} for creation".format(container))
            containers_to_create.append(container)
        else:
            logger.debug(
                "Machine {} is not provided by LXC, skipping LXC container creation"
                .format(container))

    # Create it
    client = get_lxd_client()
    for container in containers_to_create:
        logger.debug(
            "Generating LXC config for container {}".format(container))
        # Interface config
        # First add eth0 (default), which does nothing
        device_config = {"eth0": {"type": "none"}}
        # Then for each interface in the config add the configuration for that interface to the interfaces_config dict
        for inet_name, inet_config in config["machines"][container][
                "interfaces"].items():
            device_config[inet_name] = {
                "name":
                inet_name,  # The name of the interface inside the instance
                "host_name":
                "{}-{}".format(
                    container,
                    inet_name),  # The name of the interface inside the host
                "parent":
                "{}{}".format(
                    settings.VNET_BRIDGE_NAME,
                    inet_config["bridge"]),  # The name of the host device
                "type":
                "nic",
                "nictype":
                "bridged",
                "hwaddr":
                inet_config["mac"],
            }
        container_config = {
            "name": container,
            "source": {
                "alias": settings.LXC_BASE_IMAGE_ALIAS,
                "type": "image"
            },
            "ephemeral": False,
            "config": {
                "user.network-config": "disabled"
            },
            "devices": device_config,
            "profiles": [settings.LXC_VNET_PROFILE],
        }
        logger.info("Creating LXC container {}".format(container))
        # TODO: Make this nicer by not waiting here but doing the configuration after we've created all containers
        client.containers.create(container_config, wait=True)
        place_lxc_interface_configuration_on_container(config, container)

    # Check with the user if it is okay to overwrite config files
    if containers_already_created:
        request_confirmation(
            message=
            "Some containers already existed, the next operation will overwrite network, "
            "host and user config files on those containers", )
Example #14
0
def configure_lxc_base_machine(config):
    """
    Configure the LXC base machine to get a fully functional VNet base machine which we can make an image from
    :param dict config: The config generated by get_config()
    :raises RuntimeError: If the base machine is started without networking/dns
    """
    logger.info(
        "Configuring LXC base machine {}, this might take a while".format(
            settings.LXC_BASE_IMAGE_MACHINE_NAME))
    client = get_lxd_client()
    machine = client.containers.get(settings.LXC_BASE_IMAGE_MACHINE_NAME)

    def execute_and_log(command, **kwargs):
        result = machine.execute(shlex.split(command), **kwargs)
        logger.debug(result)
        return result

    # Check for DNS
    logger.debug("Checking for DNS connectivity")
    dns = False
    for _ in range(0, settings.LXC_MAX_STATUS_WAIT_ATTEMPTS):
        if execute_and_log("host -t A google.com")[0] == 0:
            dns = True
            break
        # No DNS connectivity (yet), try again
        sleep(2)
    if not dns:
        # Shutdown base if DNS check fails
        logger.debug("Stopping base machine")
        machine.stop()
        raise RuntimeError(
            "Base machine started without working DNS, unable to continue")

    # Set the FRR routing source and key
    execute_and_log(
        "bash -c 'curl -s https://deb.frrouting.org/frr/keys.asc | apt-key add'"
    )
    execute_and_log(
        "bash -c 'echo deb https://deb.frrouting.org/frr $(lsb_release -s -c) {} | tee -a /etc/apt/sources.list.d/frr.list'"
        .format(settings.FRR_RELEASE))

    # Update and install packages
    execute_and_log("apt-get update")
    execute_and_log(
        "apt-get upgrade -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'",
        environment={"DEBIAN_FRONTEND": "noninteractive"},
    )
    execute_and_log(
        "apt-get install -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' {}"
        .format(" ".join(config["providers"]["lxc"]["guest_packages"])),
        environment={"DEBIAN_FRONTEND": "noninteractive"},
    )

    # Disable radvd by default
    execute_and_log("systemctl disable radvd")
    # Disable cloud init messing with our networking
    execute_and_log(
        "bash -c 'echo network: {config: disabled} > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg'"
    )
    # Set the default VTYSH_PAGER
    execute_and_log("bash -c 'export VTYSH_PAGER=more >> ~/.bashrc'")
    # Make all files in the FRR dir owned by the frr user
    execute_and_log(
        "bash -c 'echo -e \"#!/bin/bash\nchown -R frr:frr /etc/frr\nsystemctl restart frr\" > /etc/rc.local; chmod +x /etc/rc.local'"
    )
    # All done, stop the container
    machine.stop(wait=True)
    logger.debug("LXC base machine {} successfully configured".format(
        settings.LXC_BASE_IMAGE_MACHINE_NAME))