Guest post by: Adam Friedman

Infrastructure as Code (IAC) is one of the important foundations for cloud automation. In this post we will walk through a minimal configuration scenario to illustrate a practical application of IAC using a tool called Terraform on Dimension Data cloud (MCP). This walk through should demonstrate that Terraform can be used to manage both temporary and more permanent deployments of infrastructure in Dimension Data cloud via its management layer – CloudControl.

Terraform is a tool for declarative management of virtualized infrastructure (think of it as Chef but focused more on infrastructure rather than configuration management).
It can be used to define the infrastructure that comprises an environment, and then manage that infrastructure on an ongoing basis.

CloudControl is the Dimension Data MCP management layer that provides both a UI and API to manage resources. CloudControl API’s have been integrated into several popular automation tools and libraries such as Ansible, Apache Libcloud, Powershell, among others.

Install and Configure

To install Terraform, please follow the directions outlined here.  Detailed documentation for Terraform can be found on the product website.

You will also need to download and configure provider (native integration for a cloud platform provider) support for CloudControl (Dimension Data MCP). Download the binary for your OS and follow the instructions under the Getting Started section.

For detailed documentation on Dimension Data provider usage please refer to the GitHub repo.

Usage

The displayed configuration (in HCL format) below will create:

  •  A network domain
  •  A VLAN
  • A server with its primary network adapter attached to the VLAN
provider "ddcloud" {
  region = "AU"
}

resource "ddcloud_networkdomain" "test-domain" {
  name        = "my-terraform-domain-1"
  description = "My Terraform test domain."
  datacenter  = "AU9"
}

resource "ddcloud_vlan" "test-vlan" {
  name        = "my-terraform-vlan"
  description = "My Terraform test VLAN."

  networkdomain = "${ddcloud_networkdomain.test-domain.id}"

  ipv4_base_address = "192.168.17.0"
  ipv4_prefix_size  = 24 # 255.255.255.0 = 192.168.17.1 -> 192.168.17.254
}

resource "ddcloud_server" "test-vm" {
  name           = "my-test-vm"
  description    = "My Terraform test VM."
  admin_password = "my admin password"

  memory_gb = 8
  cpu_count = 2

  networkdomain        = "${ddcloud_networkdomain.test-domain.id}"
  primary_adapter_vlan = "${ddcloud_vlan.test-vlan.id}" # Will use first available IPv4 address on this VLAN.
  dns_primary          = "8.8.8.8"
  dns_secondary        = "8.8.4.4"

  os_image_name        = "CentOS 7 64-bit 2 CPU"

  disk {
    scsi_unit_id = 0
    size_gb      = 10
  }

  tag {
    name  = "role"
    value = "tf-test"
  }
}

Evident in the output, Terraform usually treats each resource in CloudControl (or any other system) as a discrete component that can be created, managed, and destroyed.

Resources can also use properties from other resources. Terraform will keep track of the resulting dependencies between them.

If you run terraform plan, the following output is produced:

 + ddcloud_networkdomain.test-domain
 datacenter:       "AU9"
 description:      "My Terraform test domain."
 name:             "my-terraform-domain-1"
 nat_ipv4_address: ""
 plan:             "ESSENTIALS"

+ ddcloud_server.test-vm
 admin_password:              ""
 auto_start:                  "false"
 cores_per_cpu:               ""
 cpu_count:                   "2"
 cpu_speed:                   ""
 customer_image_id:           ""
 customer_image_name:         ""
 description:                 "My Terraform test VM."
 disk.#:                      "1"
 disk.219226128.disk_id:      ""
 disk.219226128.scsi_unit_id: "0"
 disk.219226128.size_gb:      "10"
 disk.219226128.speed:        "STANDARD"
 dns_primary:                 "8.8.8.8"
 dns_secondary:               "8.8.4.4"
 memory_gb:                   "8"
 name:                        "my-test-vm"
 networkdomain:               "${ddcloud_networkdomain.test-domain.id}"
 os_image_id:                 ""
 os_image_name:               "CentOS 7 64-bit 2 CPU"
 primary_adapter_ipv4:        ""
 primary_adapter_ipv6:        ""
 primary_adapter_vlan:        "${ddcloud_vlan.test-vlan.id}"
 public_ipv4:                 ""
 tag.#:                       "1"
 tag.3046084505.name:         "role"
 tag.3046084505.value:        "tf-test"

+ ddcloud_vlan.test-vlan
 description:       "My Terraform test VLAN."
 ipv4_base_address: "192.168.17.0"
 ipv4_prefix_size:  "24"
 ipv6_base_address: ""
 ipv6_prefix_size:  ""
 name:              "my-terraform-vlan"
 networkdomain:     "${ddcloud_networkdomain.test-domain.id}"

Plan: 3 to add, 0 to change, 0 to destroy.

Let’s run terraform apply. Terraform will now create these resources for you.

ddcloud_networkdomain.test-domain: Creating...
 datacenter:       "" => "AU9"
 description:      "" => "My Terraform test domain."
 name:             "" => "my-terraform-domain-1"
 nat_ipv4_address: "" => ""
 plan:             "" => "ESSENTIALS"
 ddcloud_networkdomain.test-domain: Creation complete
 ddcloud_vlan.test-vlan: Creating...
 description:       "" => "My Terraform test VLAN."
 ipv4_base_address: "" => "192.168.17.0"
 ipv4_prefix_size:  "" => "24"
 ipv6_base_address: "" => ""
 ipv6_prefix_size:  "" => ""
 name:              "" => "my-terraform-vlan"
 networkdomain:     "" => "11b5cf11-ccbb-460c-ad0a-4093029c0aff"
 ddcloud_vlan.test-vlan: Creation complete
 ddcloud_server.test-vm: Creating...
 admin_password:              "" => ""
 auto_start:                  "" => "false"
 cores_per_cpu:               "" => ""
 cpu_count:                   "" => "2"
 cpu_speed:                   "" => ""
 customer_image_id:           "" => ""
 customer_image_name:         "" => ""
 description:                 "" => "My Terraform test VM."
 disk.#:                      "" => "1"
 disk.219226128.disk_id:      "" => ""
 disk.219226128.scsi_unit_id: "" => "0"
 disk.219226128.size_gb:      "" => "10"
 disk.219226128.speed:        "" => "STANDARD"
 dns_primary:                 "" => "8.8.8.8"
 dns_secondary:               "" => "8.8.4.4"
 memory_gb:                   "" => "8"
 name:                        "" => "my-test-vm"
 networkdomain:               "" => "11b5cf11-ccbb-460c-ad0a-4093029c0aff"
 os_image_id:                 "" => ""
 os_image_name:               "" => "CentOS 7 64-bit 2 CPU"
 primary_adapter_ipv4:        "" => ""
 primary_adapter_ipv6:        "" => ""
 primary_adapter_vlan:        "" => "3794f6e3-ee89-431b-afea-b77b0ceca9bc"
 public_ipv4:                 "" => ""
 tag.#:                       "" => "1"
 tag.3046084505.name:         "" => "role"
 tag.3046084505.value:        "" => "tf-test"
 ddcloud_server.test-vm: Creation complete

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

This is certainly a convenient way to deploy infrastructure (especially if there’s a lot of it).
But what if you want to make just one change? Let’s alter the name of the network domain.

resource "ddcloud_networkdomain" "test-domain" {
 # name = "my-terraform-domain-1"
 name   = "the-terraform-domain"

# .... other properties removed for clarity
 }

Run terraform plan again, and look at the output.

~ ddcloud_networkdomain.test-domain
 name: "my-terraform-domain-1" => "my-terraform-domain"

Plan: 0 to add, 1 to change, 0 to destroy.

Terraform has examined the existing resources in CloudControl, compared them to the local configuration, and correctly detected that the only thing that has changed is the name of the network domain. Run terraform apply again, and note that all it does is update the network domain name.

ddcloud_networkdomain.test-domain: Modifying...
 name: "my-terraform-domain-1" => "the-terraform-domain"
 ddcloud_networkdomain.test-domain: Modifications complete

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

Now try changing the value of the server’s `dns_primary` property.

resource "ddcloud_server" "test-vm" {
 # .... other properties removed for clarity

# dns_secondary = "8.8.4.4"
 dns_secondary   = "8.8.1.1"

# .... other properties removed for clarity
 }

Run terraform plan and observe the following output:

-/+ ddcloud_server.test-vm
 admin_password:              "" => "" (attribute changed)
 auto_start:                  "false" => "false"
 cores_per_cpu:               "1" => ""
 cpu_count:                   "2" => "2"
 cpu_speed:                   "STANDARD" => ""
 customer_image_id:           "" => ""
 customer_image_name:         "" => ""
 description:                 "My Terraform test VM." => "My Terraform test VM."
 disk.#:                      "1" => "1"
 disk.219226128.disk_id:      "c325711d-003c-4aa1-bc0c-fa2cd0443fc1" => ""
 disk.219226128.scsi_unit_id: "0" => "0"
 disk.219226128.size_gb:      "10" => "10"
 disk.219226128.speed:        "STANDARD" => "STANDARD"
 dns_primary:                 "8.8.8.8" => "8.8.8.8"
 dns_secondary:               "8.8.4.4" => "8.8.1.1" (forces new resource)
 memory_gb:                   "8" => "8"
 name:                        "my-test-vm" => "my-test-vm"
 networkdomain:               "11b5cf11-ccbb-460c-ad0a-4093029c0aff" => "11b5cf11-ccbb-460c-ad0a-4093029c0aff"
 os_image_id:                 "e1b4e0cc-35ba-47be-a2d7-1b5601b87119" => ""
 os_image_name:               "CentOS 7 64-bit 2 CPU" => "CentOS 7 64-bit 2 CPU"
 primary_adapter_ipv4:        "192.168.17.6" => ""
 primary_adapter_ipv6:        "2402:9900:111:1454:5b20:7083:820b:a24e" => ""
 primary_adapter_vlan:        "3794f6e3-ee89-431b-afea-b77b0ceca9bc" => "3794f6e3-ee89-431b-afea-b77b0ceca9bc"
 public_ipv4:                 "" => ""
 tag.#:                       "1" => "1"
 tag.3046084505.name:         "role" => "role"
 tag.3046084505.value:        "tf-test" => "tf-test"

Plan: 1 to add, 0 to change, 1 to destroy.

Note that, this time, Terraform has detected that you’ve attempted to change a property that cannot be changed after deployment. It will therefore offer to destroy and re-create the server if you choose to proceed.

Let’s clean up by running terraform destroy and typing yes when prompted.

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

ddcloud_server.test-vm: Destroying...
ddcloud_server.test-vm: Destruction complete
ddcloud_vlan.test-vlan: Destroying...
ddcloud_vlan.test-vlan: Destruction complete
ddcloud_networkdomain.test-domain: Destroying...
ddcloud_networkdomain.test-domain: Destruction complete.

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

If now you confirm via the CloudControl UI you will see that the network domain and its contents have been deleted.

This concludes the walk through of a minimal configuration scenario using Terraform. Hopefully this practical example gives you an appreciation of what Infrastructure as Code brings to bear. In part 2 of this series we will build on this scenario and explore how Ansible can be utilized as a Configuration Management tool to deploy applications in concert with Terraform to form a single comprehensive solution for automating infrastructure and applications.