OpenStack — Inmanta 2017.3dev documentation

# OpenStack

The openstack module provides support for managing various resources on OpenStack, including virtual machines, networks, routers, …

This guide explains how to start virtual machines on OpenStack.

## Prerequisites

This tutorial requires you to have an account on an OpenStack. The example below loads the required credentials from environment variables, just like the OpenStack command line tools. Additionally, the following parameters are also required:

ssh_public_key | Your public ssh key (the key itself, not the name of the file it is in)
network_name | The name of the Openstack network to connect the VM to
subnet_name | The name of the Openstack subnet to connect the VM to
network_address | The network address of the subnet above
flavor_name | The name of the Openstack flavor to create the VM from
image_id | The ID of the Openstack image to boot the VM from
os | The OS of the image

The model below exposes these parameters at the top of the code snippet.

## Creating machines

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    import openstack
    import ssh
    import redhat
    import ubuntu

    ## Edit this parameters
    image_id = ""
    network_name = ""
    subnet_name = ""
    network_address = ""

    flavor_name = ""
    ssh_public_key=""

    # change OS parameter to match the actual image. If an OS is not modelled in an existing module,
    # std::linux can be used for example. However, other modules might not have support for a
    # generic os definition such as std::linux
    os = redhat::fedora23
    ## End edit

    # register ssh key
    ssh_key = ssh::Key(name="mykey", public_key=ssh_public_key)

    # Define the OpenStack provider to use
    provider = openstack::Provider(name="iaas_openstack", connection_url=std::get_env("OS_AUTH_URL"),
                                   username=std::get_env("OS_USERNAME"),
                                   password=std::get_env("OS_PASSWORD"),
                                   tenant=std::get_env("OS_PROJECT_NAME"))

    # Define the project/tenant to boot the VM in, but do not let inmanta manage it
    project = openstack::Project(provider=provider, name=provider.tenant, description="", enabled=true,
                                 managed=false)

    # Define the network objects to connect the virtual machine to but again, do not manage them
    net = openstack::Network(provider=provider, project=project, name=network_name, managed=false)
    subnet = openstack::Subnet(provider=provider, project=project, network=net, dhcp=true, managed=false
                               name=subnet_name, network_address=network_address)

    # Define the virtual machine
    vm = openstack::Host(provider=provider, project=project, key_pair=ssh_key, name="testhost",
                         image=image_id, os=os, flavor=flavor_name, user_data="", subnet=subnet)

## Getting the agent on the machine

The user_data attribute of the `openstack::VirtualMachine` entity can inject a shell script that is executed at first boot of the virtual machine (through cloud-init). Below is an example script to install the inmanta agent (from RPM) and let it connect back to the management server.

    #!/bin/bash

    hostname {{ name }}
    setenforce 0

    cat > /etc/yum.repos.d/inmanta.repo <<EOF
    [bartvanbrabant-inmanta]
    name=Copr repo for inmanta owned by bartvanbrabant
    baseurl=https://copr-be.cloud.fedoraproject.org/results/bartvanbrabant/inmanta/fedora-\$releasever-\$basearch/
    type=rpm-md
    skip_if_unavailable=True
    gpgcheck=1
    gpgkey=https://copr-be.cloud.fedoraproject.org/results/bartvanbrabant/inmanta/pubkey.gpg
    repo_gpgcheck=0
    enabled=1
    enabled_metadata=1
    EOF

    dnf install -y python3-inmanta-agent

    cat > /etc/inmanta/agent.cfg <<EOF
    [config]
    heartbeat-interval = 60
    fact-expire = 60
    state-dir=/var/lib/inmanta
    environment={{ env_id }}
    agent-names=\$node-name
    [agent_rest_transport]
    port={{port}}
    host={{env_server}}
    EOF

    systemctl start inmanta-agent
    systemctl enable inmanta-agent

## Pushing config to the machine

To install config:

    #put a file on the machine
    std::ConfigFile(host = host1, path="/tmp/test", content="I did it!")

## Actual usage

Creating instances of `openstack::Host`, as shown above requires many parameters and relations, creating a model that is hard to read. Often, these parameters are all the same within a single model. This means that Inmanta can encapsulate this complexity.

In a larger model, a new `Host` type can encapsulate all settings that are the same for all hosts. Additionally, an entity that represents the infrastructure can hold shared configuration such as the provider, monitoring, shared networks, global parameters,…)

For example (full source here)

Applied to the example above the main file is reduced to:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import mymodule
    import ssh
    import redhat
    import ubuntu

    ## Edit this parameters
    image_id = ""
    network_name = ""
    subnet_name = ""
    network_address = ""

    flavor_name = ""
    ssh_public_key=""

    # change OS parameter to match the actual image. If an OS is not modelled in an existing module,
    # std::linux can be used for example. However, other modules might not have support for a
    # generic os definition such as std::linux
    os = redhat::fedora23
    ## End edit

    # register ssh key
    ssh_key = ssh::Key(name="mykey", public_key=ssh_public_key)

    # create the cluster
    cluster = mymodule::MyCluster(network_name=network_name, subnet_name=subnet_name,
                                  image_id=image_id, flavor=flavor_name, key=ssh_key,
                                  network_address=network_address, os=os)

    # make a vm!
    host1 = mymodule::MyHost(name="testhost", cluster=cluster)

With the following module:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    import openstack
    import ssh

    entity MyCluster:
        """
            A cluster object that represents all shared config and infrastructure,
            including connecting to OpenStack.
        """
        string network_name
        string subnet_name
        string network_address
        string image_id
        string flavor
    end

    #input: the ssh key for all VMs
    MyCluster.key [1] -- ssh::Key

    #input: the OS for all VMs
    MyCluster.os [1] -- std::OS

    #internal: objects needed to construct hosts
    MyCluster.provider [1] -- openstack::Provider
    MyCluster.project [1] -- openstack::Project
    MyCluster.net [1] -- openstack::Network
    MyCluster.subnet [1] -- openstack::Subnet

    implementation connection for MyCluster:
        # Define the OpenStack provider to use
        self.provider = openstack::Provider(name="iaas_openstack",
                                            connection_url=std::get_env("OS_AUTH_URL"),
                                            username=std::get_env("OS_USERNAME"),
                                            password=std::get_env("OS_PASSWORD"),
                                            tenant=std::get_env("OS_PROJECT_NAME"))

        # Define the project/tenant to boot the VM in, but do not let inmanta manage it
        self.project = openstack::Project(provider=self.provider, name=self.provider.tenant,
                                          description="", enabled=true, managed=false)

        # Define the network objects to connect the virtual machine to but again, do not manage them
        self.net = openstack::Network(provider=self.provider, project=self.project,
                                      name=self.network_name, managed=false)
        self.subnet = openstack::Subnet(provider=self.provider, project=self.project,
                                        network=self.net, dhcp=true, name=self.subnet_name,
                                        network_address=self.network_address, managed=false)
    end

    implement MyCluster using connection

    #define our own host type
    entity MyHost extends openstack::Host:
    end

    #input: the cluster object
    MyCluster.hosts [0:] -- MyHost.cluster [1]

    implementation myhost for MyHost:
        #wire up all config for agent injection
        env_name = std::environment_name()
        env_id = std::environment()
        env_server = std::environment_server()
        port = std::server_port()

        #wire up all config for vm creation
        self.provider = cluster.provider
        self.project = cluster.project
        self.image = cluster.image_id
        self.subnet = cluster.subnet
        self.user_data = std::template("mymodule/user_data.tmpl")
        self.key_pair = cluster.key
        self.os = cluster.os
        self.flavor = cluster.flavor
    end

    # use our implemenation
    # and also the catchall std::hostDefaults
    # and the openstackVM implementation that sets the ip and create the eth0 port
    implement MyHost using myhost, std::hostDefaults, openstack::openstackVM, openstack::eth0Port

If this were not an example, we would make the following changes:

- hardcode the `image_id` and `os` (and perhaps `flavor`) into the defintion of `myhost`.
- the parameters on top would be moved to either a forms or filled in directly into the constructor.
- use `std::password` to store passwords, to prevent accidential check-ins with passwords in the source