TL;DR: Second of a two post series looking at automation of an openstack project with Terraform, using the new Terraform OpenStack Provider.

With the Openstack provider for Terraform being close to accepted into the Terraform release, it’s time to unleash it’s power on the Cisco Openstack-based Cloud..

In this post, we will:

  • Write a terraform ‘.TF’ file to describe our desired deployment state including;
    • Neutron networks/subnets
    • Neutron gateways
    • Keypairs and Security Groups
    • Virtual machines and Volumes
    • Virtual IP’s
    • Load balancers (LBaaS).
  • Have terraform deploy, modify and rip down our infrastructure.

If you don’t have the terraform openstack beta provider available, you’ll want to read Part 1 of this series.

Terraform Intro

Terraform “provides a common configuration to launch infrastructure“. From IaaS instances and virtual networks to DNS entries and e-mail configuration.

The idea being that a single Terraform deployment file can leverage multiple providers to describe your entire application infrastructure in one deployment tool; even if your DNS, LB and Compute resources come from three different providers.

Support for different infrastructure types is supported by provider modules, it’s the Openstack provider we’re focused on testing here.

If you’re not sure why you want to use Terraform, you’re probably best getting off here and having a look around Terraform.io first!

Terraform Configuration Files

Terraform configuration files describe your desired infrastructure state, built up of multiple resources, using one or more providers.

Configuration files are a custom, but easy to read format with a .TF extension. (They can also be written in JSON for machine generated content.)

Generally, a configuration file will hold necessary parameters for any providers needed, followed by a number of resources from those providers.

Below is a simple example with one provider (Openstack) and one resource (an SSH public key to be uploaded to our Openstack tenant)

# Basic Terraform configuration using the Openstack provider


provider "openstack" {
     user_name = "<OS_USERNAME>"
     tenant_name = "<OS_TENANT_NAME>"
     auth_url = "https://<KEYSTONE-AUTH-URI>:5000/v2.0"
     password = "<OS_PASSWORD>"
}

#
# Resource - KeyPair
# Creates a new keypair in our openstack tenant.
# Will show up in OpenStack as "tf-keypair-1"
# Can be referenced elsewhere in terraform configuration as "keypair1"
#

resource "openstack_compute_keypair_v2" "keypair1" {
    name = "tf-keypair-1"
    region = ""
    public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRjB9O5zbl82XMyt0Lt2VGuap5fFo4F6mTbmNhHI18JTd816vLl5Gl3VptHj1BacLkGtlNpv8V9zF+r8t2uo1hOjexnWqWKigy7yb5mvNYNwLiApxxdXBbJsDWOvlfRpWx3pw2Zv+BkAAEYrftCmdcg8Ax0JhzepRIHVutY/ZefxvU1T2q8tQjLeEnFvOVZgccDivlU2Alv/hw0WYwPOBANwMyqMaZCzZG+h3U+XK22zwKQVLempFBEZkVGr7Tkfp4gyrM1GdDGOKffmRMnFL+k250SDZSUT1lQblW+Q8wRJat3s8JOHf8hYgDxzsEEK2yA3JT+fOW8TXQJiaTermT"
}

Save the above as demo1.tf and replace the following placeholders with your own Openstack environment login details.

<OS_USERNAME> - Openstack login user
<OS_TENANT_NAME> - Openstack tenant/project name
<KEYSTONE-AUTH-URI> - Keystone API URI
<OS_PASSWORD> - Openstack login password


Now run $terraform plan  in the same directory as your demo1.tf  file. Terraform will tell you what it’s going to do (add/remove/update resources), based on checking the current state of the infrastructure:

[email protected] tfdemo $ terraform plan
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ openstack_compute_keypair_v2.keypair1
    name:       "" => "tf-keypair-1"
    public_key: "" => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRjB9O5zbl82XMyt0Lt2VGuap5fFo4F6mTbmNhHI18JTd816vLl5Gl3VptHj1BacLkGtlNpv8V9zF+r8t2uo1hOjexnWqWKigy7yb5mvNYNwLiApxxdXBbJsDWOvlfRpWx3pw2Zv+BkAAEYrftCmdcg8Ax0JhzepRIHVutY/ZefxvU1T2q8tQjLeEnFvOVZgccDivlU2Alv/hw0WYwPOBANwMyqMaZCzZG+h3U+XK22zwKQVLempFBEZkVGr7Tkfp4gyrM1GdDGOKffmRMnFL+k250SDZSUT1lQblW+Q8wRJat3s8JOHf8hYgDxzsEEK2yA3JT+fOW8TXQJiaTermT"

Terraform checks, the keypair doesn’t already exist on our openstack provider, so a new resource is going to be created if we apply our infrastructure… good!

Terraform Apply!

[email protected] tfdemo $ terraform apply
openstack_compute_keypair_v2.keypair1: Creating...
  name:       "" => "tf-keypair-1"
  public_key: "" => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRjB9O5zbl82XMyt0Lt2VGuap5fFo4F6mTbmNhHI18JTd816vLl5Gl3VptHj1BacLkGtlNpv8V9zF+r8t2uo1hOjexnWqWKigy7yb5mvNYNwLiApxxdXBbJsDWOvlfRpWx3pw2Zv+BkAAEYrftCmdcg8Ax0JhzepRIHVutY/ZefxvU1T2q8tQjLeEnFvOVZgccDivlU2Alv/hw0WYwPOBANwMyqMaZCzZG+h3U+XK22zwKQVLempFBEZkVGr7Tkfp4gyrM1GdDGOKffmRMnFL+k250SDZSUT1lQblW+Q8wRJat3s8JOHf8hYgDxzsEEK2yA3JT+fOW8TXQJiaTermT"
openstack_compute_keypair_v2.keypair1: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

Success! At this point you can check Openstack to confirm our new keypair exists in the IaaS:

[email protected] tfdemo $ nova keypair-list
+--------------------+---------------
| Name               | Fingerprint   
+--------------------+---------------
| tf-keypair-1       | 03:2b:0d:c3:f1
+--------------------+---------------

 

Terraform State

Future deployments of this infrastructure will check the state first, running $terraform plan  again shows no changes, as our single resource already exists in Openstack.

[email protected] tfdemo $ terraform plan
Refreshing Terraform state prior to plan...

openstack_compute_keypair_v2.keypair1: Refreshing state... (ID: tf-keypair-1)

No changes. Infrastructure is up-to-date. This means that Terraform
could not detect any differences between your configuration and
the real physical resources that exist. As a result, Terraform
doesn't need to do anything.

That’s basic terraform deployment covered using the openstack provider.

Adding More Resources

The resource we deployed above was ‘openstack_compute_keypair_v2 ‘. Resource types are named by the author of a given plugin! not centrally by terraform (which means TF config files are not re-usable between differing provider infrastructures).

Realistically this just means you need to read the doc of the provider(s) you choose to use.

Here are some openstack provider resource types we’ll use for the next demo:

“openstack_compute_keypair_v2”
“openstack_compute_secgroup_v2”
“openstack_networking_network_v2” 
“openstack_networking_subnet_v2”
“openstack_networking_router_v2”
“openstack_networking_router_interface_v2”
“openstack_compute_floatingip_v2”
“openstack_compute_instance_v2”
“openstack_lb_monitor_v1”
“openstack_lb_pool_v1”
“openstack_lb_vip_v1”

If you are familiar with Openstack, then their purpose should be clear!

The following Terraform configuration will build on our existing configuration to:

  • Upload a keypair
  • Create a security group
    • SSH and HTTPS in, plus all TCP in from other VM’s in same group.
  • Create a new Quantum network and Subnet
  • Create a new Quantum router with an external gateway
  • Assign the network to the router (router interface)
  • Request two floating IP’s into our Openstack project
  • Spin up three instances of CentOS7 based on an existing image in glance
    • With sample metadata provided in our .tf configuration file
    • Assigned to the security group terraform created
    • Using the keypair terraform created
    • Assigned to the network terraform created
      • Assigned static IP’s 100-103
    • The first two instances will be bound to the two floating IP’s
  • Create a Load Balancer Pool, Monitor and VIP.
# Testing the OpenStack provider from merge of
# https://github.com/hashicorp/terraform/pull/924


provider "openstack" {
     user_name = "<OS_USERNAME>"
     tenant_name = "<OS_TENANT_NAME>"
     auth_url = "https://<KEYSTONE-AUTH-URI>:5000/v2.0"
     password = "<OS_PASSWORD>"
}


#
# Create a new keypair in our openstack tenant
#

resource "openstack_compute_keypair_v2" "keypair1" {
    name = "tf-keypair-1"
    region = ""
    public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRjB9O5zbl82XMyt0Lt2VGuap5fFo4F6mTbmNhHI18JTd816vLl5Gl3VptHj1BacLkGtlNpv8V9zF+r8t2uo1hOjexnWqWKigy7yb5mvNYNwLiApxxdXBbJsDWOvlfRpWx3pw2Zv+BkAAEYrftCmdcg8Ax0JhzepRIHVutY/ZefxvU1T2q8tQjLeEnFvOVZgccDivlU2Alv/hw0WYwPOBANwMyqMaZCzZG+h3U+XK22zwKQVLempFBEZkVGr7Tkfp4gyrM1GdDGOKffmRMnFL+k250SDZSUT1lQblW+Q8wRJat3s8JOHf8hYgDxzsEEK2yA3JT+fOW8TXQJiaTermT"
}


#
# Create a security group
#

resource "openstack_compute_secgroup_v2" "tf_sec_1" {
    region = ""
    name = "tf_sec_1"
    description = "Security Group Via Terraform"
    rule {
        from_port = 22
        to_port = 22
        ip_protocol = "tcp"
        cidr = "0.0.0.0/0"
    }
    rule {
        from_port = 1
        to_port = 65535
        ip_protocol = "tcp"
        self = true
    }
    rule {
        from_port = 443
        to_port = 443
        ip_protocol = "tcp"
        cidr = "0.0.0.0/0"
    }
}


#
# Create a Network
#
resource "openstack_networking_network_v2" "tf_network" {
    region = ""
    name = "tf_network"
    admin_state_up = "true"
}

#
# Create a subnet in our new network
# Notice here we use a TF variable for the name of our network above.
#
resource "openstack_networking_subnet_v2" "tf_net_sub1" {
    region = ""
    network_id = "${openstack_networking_network_v2.tf_network.id}"
    cidr = "192.168.1.0/24"
    ip_version = 4
}

#
# Create a router for our network
#
resource "openstack_networking_router_v2" "tf_router1" {
    region = ""
    name = "tf_router1"
    admin_state_up = "true"
    external_gateway = "ca80ff29-4f29-49a5-aa22-549f31b09268"
}

#
# Attach the Router to our Network via an Interface
#
resource "openstack_networking_router_interface_v2" "tf_rtr_if_1" {
    region = ""
    router_id = "${openstack_networking_router_v2.tf_router1.id}"
    subnet_id = "${openstack_networking_subnet_v2.tf_net_sub1.id}"
}

#
# Create some Openstack Floating IP's for our VM's
#
resource "openstack_compute_floatingip_v2" "fip_1" {
    region = ""
    pool = "public-floating-601"
}

resource "openstack_compute_floatingip_v2" "fip_2" {
    region = ""
    pool = "public-floating-601"
}

#
# Create a VM Instance on CentOS7 Image by name
#

resource "openstack_compute_instance_v2" "instance_1" {
    region = ""
    name = "tf_test_1"
    image_name = "centos-7_x86_64-2015-01-27-v6"
    flavor_name = "GP2-Xlarge"
    key_pair = "tf-keypair-1"
    security_groups = ["tf_sec_1"]
    metadata {
        demo = "metadata"
    }
    network {
        uuid = "${openstack_networking_network_v2.tf_network.id}"
        fixed_ip = "192.168.1.100"
    }
    floating_ip = "${openstack_compute_floatingip_v2.fip_1.address}"
}

#
# Create a VM Instance on CentOS7 Image by ID
#
resource "openstack_compute_instance_v2" "instance_2" {
    region = ""
    name = "tf_test_2"
    image_id = "43b247f3-8d79-4945-8f03-76cf0e8e7008"
    flavor_name = "GP2-Xlarge"
    key_pair = "tf-keypair-1"
    security_groups = ["tf_sec_1"]
    metadata {
        demo = "metadata"
    }
    network {
        uuid = "${openstack_networking_network_v2.tf_network.id}"
        fixed_ip = "192.168.1.101"
    }
    floating_ip = "${openstack_compute_floatingip_v2.fip_2.address}"
}

#
# Create a VM Instance on CentOS7 Image by ID
#
resource "openstack_compute_instance_v2" "instance_3" {
    region = ""
    name = "tf_test_3"
    image_id = "43b247f3-8d79-4945-8f03-76cf0e8e7008"
    flavor_name = "GP2-Medium"
    key_pair = "tf-keypair-1"
    security_groups = ["tf_sec_1"]
    metadata {
        demo = "metadata"
    }
    network {
        uuid = "${openstack_networking_network_v2.tf_network.id}"
        fixed_ip = "192.168.1.102"
    }
}


#
# Create a LB Monitor, Pool and VIP
#
resource "openstack_lb_monitor_v1" "tf_lb_mon_1" {
  region = ""
  type = "PING"
  delay = 30
  timeout = 5
  max_retries = 3
  admin_state_up = "true"
}


resource "openstack_lb_pool_v1" "tf_lb_pl_1" {
  region = ""
  name = "tf_lb_pl_1"
  protocol = "HTTP"
  subnet_id = "${openstack_networking_subnet_v2.tf_net_sub1.id}"
  lb_method = "ROUND_ROBIN"
  monitor_ids = ["${openstack_lb_monitor_v1.tf_lb_mon_1.id}"]
  member {
    address = "192.168.1.20"
    region = ""
    port = 80
    admin_state_up = "true"
  }
}

resource "openstack_lb_vip_v1" "tf_lb_vip_1" {
  region = ""
  name = "tf_lb_vip_1"
  subnet_id = "${openstack_networking_subnet_v2.tf_net_sub1.id}"
  protocol = "HTTP"
  port = 80
  pool_id = "${openstack_lb_pool_v1.tf_lb_pl_1.id}"
}

Before we go ahead and $terraform plan ; $terraform apply  this configuration.. A couple of notes.

Terraform Instance References / Variables

This configuration introduces a lot of resources, each resource may have a set of required and optional fields.

Some of these fields require the UUID/ID of other openstack resources, but as we haven’t created any of the infrastructure yet via $terraform apply , we can’t be expected to know the UUID of objects that don’t yet exist.

Terraform allows you to reference other resources in the configuration file by their terraform resource name, terraform will then order the creation of resources and dynamically fill in the required information when needed.

For example. In the following resource section, we need the ID of an Openstack Neutron network in order to create a subnet under it. The ID of the network is not known, as it doesn’t yet exist. So instead a reference to our named instance of the the openstack_network_v2 resource,  tf_network  is used and from that resource we want the ID passing to the subnet resource hence the .id  at the end.

resource "openstack_networking_subnet_v2" "tf_net_sub1" {
    region = ""
    network_id = "${openstack_networking_network_v2.tf_network.id}"
    cidr = "192.168.1.0/24"
    ip_version = 4
}

Regions

You will notice each resource has a region=””  field. This is a required field in the openstack terraform provider module for every resource (try deleting it, $terraform plan  will error).

If your openstack target is not region aware/enabled, then you must set the region to null in this way.

Environment specific knowledge

Even with dynamic referencing of ID’s explained above, you are still not going to be able to copy, paste, save and $terraform apply , as there are references in the configuration specific to my openstack environment, just like username, password and openstack API URL in demo1, in demo2 you will need to provide the following in your copy of the configuration:

  • Your own keypair public key
  • The ID of your environment’s ‘external gateway’ network for binding your Neutron router too.
  • The pool name(s) to request floating IP’s from.
  • The Name/ID of a glance image to boot the instances from.
  • The Flavour name(s) of your environment’s instances.

I have placed a sanitised version of the configuration file in a gist, with these locations clearly marked by <<USER_INPUT_NEEDED>> to make the above items easier to find/edit.

http://goo.gl/B3x1o4

[email protected] tfdemo $ wget https://gist.githubusercontent.com/matjohn2/8528cca20825fd3d6db7/raw/ea4884c12d2bcc1988305e2962c622dec960e351/demo2-openstack.tf
[email protected] tfdemo $ 
[email protected] tfdemo $ rm demo2-openstack.tf*
[email protected] tfdemo $ wget https://gist.githubusercontent.com/matjohn2/8528cca20825fd3d6db7/raw/ea4884c12d2bcc1988305e2962c622dec960e351/demo2-openstack.tf
--2015-03-27 12:38:44--  https://gist.githubusercontent.com/matjohn2/8528cca20825fd3d6db7/raw/ea4884c12d2bcc1988305e2962c622dec960e351/demo2-openstack.tf
Resolving gist.githubusercontent.com... 23.235.43.133
Connecting to gist.githubusercontent.com|23.235.43.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4899 (4.8K) [text/plain]
Saving to: 'demo2-openstack.tf'

demo2-openstack.tf            100%[=================================================>]   4.78K  --.-KB/s   in 0.001s 

2015-03-27 12:38:44 (6.58 MB/s) - 'demo2-openstack.tf' saved [4899/4899]

[email protected] tfdemo $ grep "<<USER_INPUT_NEEDED>>" -B4 demo2-openstack.tf 

provider "openstack" {
     user_name = "<<USER_INPUT_NEEDED>>"
     tenant_name = "<<USER_INPUT_NEEDED>>"
     auth_url = "<<USER_INPUT_NEEDED>>"
     password = "<<USER_INPUT_NEEDED>>"
--
resource "openstack_networking_router_v2" "tf_router1" {
    region = ""
    name = "tf_router1"
    admin_state_up = "true"
    external_gateway = "<<USER_INPUT_NEEDED>>"
--
# Create some Openstack Floating IP's for our VM's
#
resource "openstack_compute_floatingip_v2" "fip_1" {
    region = ""
    pool = "<<USER_INPUT_NEEDED>>"
--
}

resource "openstack_compute_floatingip_v2" "fip_2" {
    region = ""
    pool = "<<USER_INPUT_NEEDED>>"
--

resource "openstack_compute_instance_v2" "instance_1" {
    region = ""
    name = "tf_test_1"
    image_name = "<<USER_INPUT_NEEDED>>"
    flavor_name = "<<USER_INPUT_NEEDED>>"
--
#
resource "openstack_compute_instance_v2" "instance_2" {
    region = ""
    name = "tf_test_2"
    image_id = "<<USER_INPUT_NEEDED>>"
    flavor_name = "<<USER_INPUT_NEEDED>>"
--
#
resource "openstack_compute_instance_v2" "instance_3" {
    region = ""
    name = "tf_test_3"
    image_id = "<<USER_INPUT_NEEDED>>"
    flavor_name = "<<USER_INPUT_NEEDED>>"

Creating the Infrastructure 

With your edits to the configuration done:

[email protected] tfdemo $ terraform plan
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ openstack_compute_floatingip_v2.fip_1
    address:     "" => "<computed>"
    fixed_ip:    "" => "<computed>"
    instance_id: "" => "<computed>"
    pool:        "" => "public-floating-601"

+ openstack_compute_floatingip_v2.fip_2
    address:     "" => "<computed>"
    fixed_ip:    "" => "<computed>"
    instance_id: "" => "<computed>"
    pool:        "" => "public-floating-601"

+ openstack_compute_instance_v2.instance_1
    access_ip_v4:       "" => "<computed>"
    access_ip_v6:       "" => "<computed>"
    flavor_id:          "" => "<computed>"
    flavor_name:        "" => "GP2-Xlarge"
    floating_ip:        "" => "${openstack_compute_floatingip_v2.fip_1.address}"
    image_id:           "" => "<computed>"
    image_name:         "" => "centos-7_x86_64-2015-01-27-v6"
    key_pair:           "" => "tf-keypair-1"
    metadata.#:         "" => "1"
    metadata.demo:      "" => "metadata"
    name:               "" => "tf_test_1"
    network.#:          "" => "1"
    network.0.fixed_ip: "" => "192.168.1.100"
    network.0.uuid:     "" => "${openstack_networking_network_v2.tf_network.id}"
    security_groups.#:  "" => "1"
    security_groups.0:  "" => "tf_sec_1"

+ openstack_compute_instance_v2.instance_2
    access_ip_v4:       "" => "<computed>"
    access_ip_v6:       "" => "<computed>"
    flavor_id:          "" => "<computed>"
    flavor_name:        "" => "GP2-Xlarge"
    floating_ip:        "" => "${openstack_compute_floatingip_v2.fip_2.address}"
    image_id:           "" => "43b247f3-8d79-4945-8f03-76cf0e8e7008"
    image_name:         "" => "<computed>"
    key_pair:           "" => "tf-keypair-1"
    metadata.#:         "" => "1"
    metadata.demo:      "" => "metadata"
    name:               "" => "tf_test_2"
    network.#:          "" => "1"
    network.0.fixed_ip: "" => "192.168.1.101"
    network.0.uuid:     "" => "${openstack_networking_network_v2.tf_network.id}"
    security_groups.#:  "" => "1"
    security_groups.0:  "" => "tf_sec_1"

+ openstack_compute_instance_v2.instance_3
    access_ip_v4:       "" => "<computed>"
    access_ip_v6:       "" => "<computed>"
    flavor_id:          "" => "<computed>"
    flavor_name:        "" => "GP2-Medium"
    image_id:           "" => "43b247f3-8d79-4945-8f03-76cf0e8e7008"
    image_name:         "" => "<computed>"
    key_pair:           "" => "tf-keypair-1"
    metadata.#:         "" => "1"
    metadata.demo:      "" => "metadata"
    name:               "" => "tf_test_3"
    network.#:          "" => "1"
    network.0.fixed_ip: "" => "192.168.1.102"
    network.0.uuid:     "" => "${openstack_networking_network_v2.tf_network.id}"
    security_groups.#:  "" => "1"
    security_groups.0:  "" => "tf_sec_1"

+ openstack_compute_keypair_v2.keypair1
    name:       "" => "tf-keypair-1"
    public_key: "" => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRjB9O5zbl82XMyt0Lt2VGuap5fFo4F6mTbmNhHI18JTd816vLl5Gl3VptHj1BacLkGtlNpv8V9zF+r8t2uo1hOjexnWqWKigy7yb5mvNYNwLiApxxdXBbJsDWOvlfRpWx3pw2Zv+BkAAEYrftCmdcg8Ax0JhzepRIHVutY/ZefxvU1T2q8tQjLeEnFvOVZgccDivlU2Alv/hw0WYwPOBANwMyqMaZCzZG+h3U+XK22zwKQVLempFBEZkVGr7Tkfp4gyrM1GdDGOKffmRMnFL+k250SDZSUT1lQblW+Q8wRJat3s8JOHf8hYgDxzsEEK2yA3JT+fOW8TXQJiaTermT"

+ openstack_compute_secgroup_v2.tf_sec_1
    description:        "" => "Security Group Via Terraform"
    name:               "" => "tf_sec_1"
    rule.#:             "0" => "3"
    rule.0.cidr:        "" => "0.0.0.0/0"
    rule.0.from_port:   "" => "22"
    rule.0.id:          "" => "<computed>"
    rule.0.ip_protocol: "" => "tcp"
    rule.0.self:        "" => "0"
    rule.0.to_port:     "" => "22"
    rule.1.from_port:   "" => "1"
    rule.1.id:          "" => "<computed>"
    rule.1.ip_protocol: "" => "tcp"
    rule.1.self:        "" => "1"
    rule.1.to_port:     "" => "65535"
    rule.2.cidr:        "" => "0.0.0.0/0"
    rule.2.from_port:   "" => "443"
    rule.2.id:          "" => "<computed>"
    rule.2.ip_protocol: "" => "tcp"
    rule.2.self:        "" => "0"
    rule.2.to_port:     "" => "443"

+ openstack_lb_monitor_v1.tf_lb_mon_1
    admin_state_up: "" => "true"
    delay:          "" => "30"
    max_retries:    "" => "3"
    timeout:        "" => "5"
    type:           "" => "PING"

+ openstack_lb_pool_v1.tf_lb_pl_1
    lb_method:                        "" => "ROUND_ROBIN"
    member.#:                         "" => "1"
    member.2984594024.address:        "" => "192.168.1.20"
    member.2984594024.admin_state_up: "" => "1"
    member.2984594024.port:           "" => "80"
    member.2984594024.region:         "" => ""
    member.2984594024.tenant_id:      "" => ""
    monitor_ids.#:                    "" => "<computed>"
    name:                             "" => "tf_lb_pl_1"
    protocol:                         "" => "HTTP"
    subnet_id:                        "" => "${openstack_networking_subnet_v2.tf_net_sub1.id}"

+ openstack_lb_vip_v1.tf_lb_vip_1
    name:      "" => "tf_lb_vip_1"
    pool_id:   "" => "${openstack_lb_pool_v1.tf_lb_pl_1.id}"
    port:      "" => "80"
    protocol:  "" => "HTTP"
    subnet_id: "" => "${openstack_networking_subnet_v2.tf_net_sub1.id}"

+ openstack_networking_network_v2.tf_network
    admin_state_up: "" => "true"
    name:           "" => "tf_network"

+ openstack_networking_router_interface_v2.tf_rtr_if_1
    router_id: "" => "${openstack_networking_router_v2.tf_router1.id}"
    subnet_id: "" => "${openstack_networking_subnet_v2.tf_net_sub1.id}"

+ openstack_networking_router_v2.tf_router1
    admin_state_up:   "" => "true"
    external_gateway: "" => "ca80ff29-4f29-49a5-aa22-549f31b09268"
    name:             "" => "tf_router1"

+ openstack_networking_subnet_v2.tf_net_sub1
    cidr:       "" => "192.168.1.0/24"
    ip_version: "" => "4"
    network_id: "" => "${openstack_networking_network_v2.tf_network.id}"

Terraform Apply! (for the final time in this post!)

[email protected] tfdemo $ terraform apply
openstack_lb_monitor_v1.tf_lb_mon_1: Creating...
  admin_state_up: "" => "true"
  delay:          "" => "30"
  max_retries:    "" => "3"
  timeout:        "" => "5"
  type:           "" => "PING"
openstack_compute_floatingip_v2.fip_2: Creating...
  address:     "" => "<computed>"
  fixed_ip:    "" => "<computed>"
  instance_id: "" => "<computed>"
  pool:        "" => "public-floating-601"
openstack_compute_floatingip_v2.fip_1: Creating...
  address:     "" => "<computed>"
  fixed_ip:    "" => "<computed>"
  instance_id: "" => "<computed>"
  pool:        "" => "public-floating-601"
openstack_networking_router_v2.tf_router1: Creating...
  admin_state_up:   "" => "true"
  external_gateway: "" => "ca80ff29-4f29-49a5-aa22-549f31b09268"
  name:             "" => "tf_router1"
openstack_networking_network_v2.tf_network: Creating...
  admin_state_up: "" => "true"
  name:           "" => "tf_network"
openstack_compute_secgroup_v2.tf_sec_1: Creating...
  description:        "" => "Security Group Via Terraform"
  name:               "" => "tf_sec_1"
  rule.#:             "0" => "3"
  rule.0.cidr:        "" => "0.0.0.0/0"
  rule.0.from_port:   "" => "22"
  rule.0.id:          "" => "<computed>"
  rule.0.ip_protocol: "" => "tcp"
  rule.0.self:        "" => "0"
  rule.0.to_port:     "" => "22"
  rule.1.from_port:   "" => "1"
  rule.1.id:          "" => "<computed>"
  rule.1.ip_protocol: "" => "tcp"
  rule.1.self:        "" => "1"
  rule.1.to_port:     "" => "65535"
  rule.2.cidr:        "" => "0.0.0.0/0"
  rule.2.from_port:   "" => "443"
  rule.2.id:          "" => "<computed>"
  rule.2.ip_protocol: "" => "tcp"
  rule.2.self:        "" => "0"
  rule.2.to_port:     "" => "443"
openstack_compute_keypair_v2.keypair1: Creating...
  name:       "" => "tf-keypair-1"
  public_key: "" => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRjB9O5zbl82XMyt0Lt2VGuap5fFo4F6mTbmNhHI18JTd816vLl5Gl3VptHj1BacLkGtlNpv8V9zF+r8t2uo1hOjexnWqWKigy7yb5mvNYNwLiApxxdXBbJsDWOvlfRpWx3pw2Zv+BkAAEYrftCmdcg8Ax0JhzepRIHVutY/ZefxvU1T2q8tQjLeEnFvOVZgccDivlU2Alv/hw0WYwPOBANwMyqMaZCzZG+h3U+XK22zwKQVLempFBEZkVGr7Tkfp4gyrM1GdDGOKffmRMnFL+k250SDZSUT1lQblW+Q8wRJat3s8JOHf8hYgDxzsEEK2yA3JT+fOW8TXQJiaTermT"
openstack_compute_keypair_v2.keypair1: Creation complete
openstack_lb_monitor_v1.tf_lb_mon_1: Creation complete
openstack_networking_network_v2.tf_network: Creation complete
openstack_compute_instance_v2.instance_3: Creating...
  access_ip_v4:       "" => "<computed>"
  access_ip_v6:       "" => "<computed>"
  flavor_id:          "" => "<computed>"
  flavor_name:        "" => "GP2-Medium"
  image_id:           "" => "43b247f3-8d79-4945-8f03-76cf0e8e7008"
  image_name:         "" => "<computed>"
  key_pair:           "" => "tf-keypair-1"
  metadata.#:         "" => "1"
  metadata.demo:      "" => "metadata"
  name:               "" => "tf_test_3"
  network.#:          "" => "1"
  network.0.fixed_ip: "" => "192.168.1.102"
  network.0.uuid:     "" => "5c4c5d9c-54ef-4440-a795-0362d7051f40"
  security_groups.#:  "" => "1"
  security_groups.0:  "" => "tf_sec_1"
openstack_networking_subnet_v2.tf_net_sub1: Creating...
  cidr:       "" => "192.168.1.0/24"
  ip_version: "" => "4"
  network_id: "" => "5c4c5d9c-54ef-4440-a795-0362d7051f40"
openstack_networking_router_v2.tf_router1: Creation complete
openstack_networking_subnet_v2.tf_net_sub1: Creation complete
openstack_lb_pool_v1.tf_lb_pl_1: Creating...
  lb_method:                        "" => "ROUND_ROBIN"
  member.#:                         "" => "1"
  member.2984594024.address:        "" => "192.168.1.20"
  member.2984594024.admin_state_up: "" => "1"
  member.2984594024.port:           "" => "80"
  member.2984594024.region:         "" => ""
  member.2984594024.tenant_id:      "" => ""
  monitor_ids.#:                    "" => "1"
  monitor_ids.430250668:            "" => "dc9c9b0e-0651-4272-a752-7953211b4857"
  name:                             "" => "tf_lb_pl_1"
  protocol:                         "" => "HTTP"
  subnet_id:                        "" => "1bfd3692-0337-4c3f-ad53-8c0b16f9cac7"
openstack_networking_router_interface_v2.tf_rtr_if_1: Creating...
  router_id: "" => "964ca19b-589c-46bf-88df-2c3463eaa697"
  subnet_id: "" => "1bfd3692-0337-4c3f-ad53-8c0b16f9cac7"
openstack_compute_floatingip_v2.fip_1: Creation complete
openstack_compute_instance_v2.instance_1: Creating...
  access_ip_v4:       "" => "<computed>"
  access_ip_v6:       "" => "<computed>"
  flavor_id:          "" => "<computed>"
  flavor_name:        "" => "GP2-Xlarge"
  floating_ip:        "" => "10.203.31.110"
  image_id:           "" => "<computed>"
  image_name:         "" => "centos-7_x86_64-2015-01-27-v6"
  key_pair:           "" => "tf-keypair-1"
  metadata.#:         "" => "1"
  metadata.demo:      "" => "metadata"
  name:               "" => "tf_test_1"
  network.#:          "" => "1"
  network.0.fixed_ip: "" => "192.168.1.100"
  network.0.uuid:     "" => "5c4c5d9c-54ef-4440-a795-0362d7051f40"
  security_groups.#:  "" => "1"
  security_groups.0:  "" => "tf_sec_1"
openstack_compute_floatingip_v2.fip_2: Creation complete
openstack_compute_instance_v2.instance_2: Creating...
  access_ip_v4:       "" => "<computed>"
  access_ip_v6:       "" => "<computed>"
  flavor_id:          "" => "<computed>"
  flavor_name:        "" => "GP2-Xlarge"
  floating_ip:        "" => "10.203.31.117"
  image_id:           "" => "43b247f3-8d79-4945-8f03-76cf0e8e7008"
  image_name:         "" => "<computed>"
  key_pair:           "" => "tf-keypair-1"
  metadata.#:         "" => "1"
  metadata.demo:      "" => "metadata"
  name:               "" => "tf_test_2"
  network.#:          "" => "1"
  network.0.fixed_ip: "" => "192.168.1.101"
  network.0.uuid:     "" => "5c4c5d9c-54ef-4440-a795-0362d7051f40"
  security_groups.#:  "" => "1"
  security_groups.0:  "" => "tf_sec_1"
openstack_networking_router_interface_v2.tf_rtr_if_1: Creation complete
openstack_lb_pool_v1.tf_lb_pl_1: Creation complete
openstack_lb_vip_v1.tf_lb_vip_1: Creating...
  name:      "" => "tf_lb_vip_1"
  pool_id:   "" => "2e5c763b-1080-485c-8677-2659b4ab1366"
  port:      "" => "80"
  protocol:  "" => "HTTP"
  subnet_id: "" => "1bfd3692-0337-4c3f-ad53-8c0b16f9cac7"
openstack_lb_vip_v1.tf_lb_vip_1: Creation complete
openstack_compute_secgroup_v2.tf_sec_1: Creation complete
openstack_compute_instance_v2.instance_3: Creation complete
openstack_compute_instance_v2.instance_2: Creation complete
openstack_compute_instance_v2.instance_1: Creation complete

Apply complete! Resources: 14 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

Enjoy your new infrastructure!

We can also confirm these items really do exist in openstack:

nova list
quantum security-group-list
quantum network-list
quantum floatingip-list
quantum subnet-list

Destroying Infrastructure

$terraform destroy  will destroy your infrastructure. I find this often needs running twice, as certain objects (subnets, security groups etc) are still in use when terraform tries to delete them.

This could simply be our terraform API calls being quicker than the state update within openstack, there is a bug open with the openstack terraform provider.

First Run:

Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

openstack_lb_monitor_v1.tf_lb_mon_1: Refreshing state... (ID: 0d2198f5-8027-416e-98a6-a5097ba456ac)
openstack_compute_floatingip_v2.fip_2: Refreshing state... (ID: 8ff7e6be-c911-4126-9e9d-48784a08e6b5)
openstack_compute_floatingip_v2.fip_1: Refreshing state... (ID: 50769d49-835a-4d77-b01a-876388572520)
openstack_networking_router_v2.tf_router1: Refreshing state... (ID: c41f237c-281c-4026-973c-3efc537caddf)
openstack_networking_network_v2.tf_network: Refreshing state... (ID: 54766d7d-84a5-40af-83c0-d0babc8d1359)
openstack_compute_secgroup_v2.tf_sec_1: Refreshing state... (ID: a05945b8-78ba-4ae4-acfa-d7c294347574)
openstack_compute_keypair_v2.keypair1: Refreshing state... (ID: tf-keypair-1)
openstack_compute_instance_v2.instance_3: Refreshing state... (ID: b4d2cbd3-b2fd-4eb0-b947-09c8b93b325c)
openstack_networking_subnet_v2.tf_net_sub1: Refreshing state... (ID: 9a6e5e51-b6b9-421a-845e-9d85e099ca75)
openstack_lb_pool_v1.tf_lb_pl_1: Refreshing state... (ID: 54162cb0-5803-4f74-a56b-bbfa4e614a18)
openstack_networking_router_interface_v2.tf_rtr_if_1: Refreshing state... (ID: 012c2ce5-7fcb-4d65-8c0f-b32d2eec8176)
openstack_compute_instance_v2.instance_1: Refreshing state... (ID: 76e8c0d8-83ef-4174-99ab-d10ba3c46e6a)
openstack_lb_vip_v1.tf_lb_vip_1: Refreshing state... (ID: 8d79ab28-5b81-4bdd-99cf-40d54730ef0e)
openstack_compute_instance_v2.instance_2: Refreshing state... (ID: 852ae42d-eff9-402d-a36f-c519b2839029)
openstack_lb_vip_v1.tf_lb_vip_1: Destroying...
openstack_compute_instance_v2.instance_3: Destroying...
openstack_compute_instance_v2.instance_2: Destroying...
openstack_compute_instance_v2.instance_1: Destroying...
openstack_networking_router_interface_v2.tf_rtr_if_1: Destroying...
openstack_compute_secgroup_v2.tf_sec_1: Destroying...
openstack_compute_keypair_v2.keypair1: Destroying...
openstack_networking_router_interface_v2.tf_rtr_if_1: Destruction complete
openstack_networking_router_v2.tf_router1: Destroying...
openstack_lb_vip_v1.tf_lb_vip_1: Destruction complete
openstack_lb_pool_v1.tf_lb_pl_1: Destroying...
openstack_compute_keypair_v2.keypair1: Destruction complete
openstack_networking_router_v2.tf_router1: Destruction complete
openstack_lb_pool_v1.tf_lb_pl_1: Destruction complete
openstack_lb_monitor_v1.tf_lb_mon_1: Destroying...
openstack_networking_subnet_v2.tf_net_sub1: Destroying...
openstack_compute_secgroup_v2.tf_sec_1: Error: Error deleting OpenStack security group: Expected HTTP response code [202 204] when accessing [DELETE https://<OPENSTACK-API>:8774/v2/b32f38ccf8834fd99427b3415a406295/os-security-groups/a05945b8-78ba-4ae4-acfa-d7c294347574], but got 400 instead
{"badRequest": {"message": "409-{u'NeutronError': {u'message': u'Security Group a05945b8-78ba-4ae4-acfa-d7c294347574 in use.', u'type': u'SecurityGroupInUse', u'detail': u''}}", "code": 400}}
openstack_lb_monitor_v1.tf_lb_mon_1: Destruction complete
openstack_networking_subnet_v2.tf_net_sub1: Error: Error deleting OpenStack Neutron Subnet: Expected HTTP response code [202 204] when accessing [DELETE https://<OPENSTACK-API>:9696/v2.0/subnets/9a6e5e51-b6b9-421a-845e-9d85e099ca75], but got 409 instead
{"NeutronError": {"message": "Unable to complete operation on subnet 9a6e5e51-b6b9-421a-845e-9d85e099ca75. One or more ports have an IP allocation from this subnet.", "type": "SubnetInUse", "detail": ""}}
openstack_compute_instance_v2.instance_2: Destruction complete
openstack_compute_floatingip_v2.fip_2: Destroying...
openstack_compute_instance_v2.instance_3: Destruction complete
openstack_compute_floatingip_v2.fip_2: Destruction complete
openstack_compute_instance_v2.instance_1: Destruction complete
openstack_compute_floatingip_v2.fip_1: Destroying...
openstack_compute_floatingip_v2.fip_1: Destruction complete
error applying plan:

1 error(s) occurred:

* Error deleting OpenStack security group.

Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.

Second Run: Remaining resources are now removed.

[email protected] tfdemo $ terraform destroy
Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

openstack_networking_network_v2.tf_network: Refreshing state... (ID: 54766d7d-84a5-40af-83c0-d0babc8d1359)
openstack_compute_secgroup_v2.tf_sec_1: Refreshing state... (ID: a05945b8-78ba-4ae4-acfa-d7c294347574)
openstack_networking_subnet_v2.tf_net_sub1: Refreshing state... (ID: 9a6e5e51-b6b9-421a-845e-9d85e099ca75)
openstack_networking_subnet_v2.tf_net_sub1: Destroying...
openstack_compute_secgroup_v2.tf_sec_1: Destroying...
openstack_networking_subnet_v2.tf_net_sub1: Destruction complete
openstack_networking_network_v2.tf_network: Destroying...
openstack_compute_secgroup_v2.tf_sec_1: Destruction complete
openstack_networking_network_v2.tf_network: Destruction complete

Apply complete! Resources: 0 added, 0 changed, 3 destroyed.

Thats all for now boys and girls!

Enjoy your weekend.

 

 

 

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.