Skip to content

Commit

Permalink
Add support for allocations
Browse files Browse the repository at this point in the history
  • Loading branch information
stbenjam committed Apr 15, 2019
1 parent c4a1010 commit ce997ae
Show file tree
Hide file tree
Showing 14 changed files with 652 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ dist: xenial
sudo: false
language: go
env:
- TF_ACC=true
- TF_ACC=true TAGS="acceptance"
go:
- 1.11.x
git:
Expand Down
4 changes: 3 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ endif
default: fmt lint build

build:
go build -ldflags "${LDFLAGS}"
go build -ldflags "${LDFLAGS}" -tags "${TAGS}"

install: default
mkdir -p ${TERRAFORM_PLUGINS}
Expand All @@ -27,10 +27,10 @@ lint: tools
go run golang.org/x/lint/golint -set_exit_status ./ironic .

test:
go test -v ./ironic
go test -tags "${TAGS}" -v ./ironic

acceptance:
TF_ACC=true go test -v ./ironic/...
TF_ACC=true go test -tags "acceptance" -v ./ironic/...

clean:
rm -f terraform-provider-ironic
Expand Down
58 changes: 54 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,29 @@

This is a terraform provider that lets you provision baremetal servers managed by Ironic.

# Usage
## Provider

Example:
Currently the provider only supports standalone noauth Ironic. At a
minimum, the Ironic endpoint URL must be specified. The user may also
optionally specify an API microversion.

```terraform
provider "ironic" {
"url" = "http://localhost:6385/v1"
"microversion" = "1.50"
url = "http://localhost:6385/v1"
microversion = "1.52"
}
```

## Resources

This provider currently implements a number of native Ironic resources,
described below.

### Nodes

A node describes a hardware resource.

```terraform
resource "ironic_node_v1" "openshift-master-0" {
name = "openshift-master-0"
target_provision_state = "active"
Expand Down Expand Up @@ -50,6 +63,43 @@ resource "ironic_node_v1" "openshift-master-0" {
}
```

## Ports

Ports may be specified as part of the node resource, or as a separate `ironic_port_v1`
declaration.

```terraform
resource "ironic_port_v1" "openshift-master-0-port-0" {
node_uuid = "${ironic_node_v1.openshift-master-0.id}"
pxe_enabled = true
address = "00:bb:4a:d0:5e:38"
}
```

## Allocation

The Allocation resource represents a request to find and allocate a Node
for deployment. The microversion must be 1.52 or later.

```terraform
resource "ironic_allocation_v1" "openshift-master-allocation" {
name = "master-${count.index}"
count = 3
resource_class = "baremetal"
candidates = [
"${ironic_node_v1.openshift-master-0.id}",
"${ironic_node_v1.openshift-master-1.id}",
"${ironic_node_v1.openshift-master-2.id}",
]
traits = [
"CUSTOM_FOO",
]
}
```

# License

Apache 2.0, See LICENSE file
7 changes: 4 additions & 3 deletions ironic/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ func Provider() terraform.ResourceProvider {
"microversion": {
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("IRONIC_MICROVERSION", "1.50"),
DefaultFunc: schema.EnvDefaultFunc("IRONIC_MICROVERSION", "1.52"),
Description: descriptions["microversion"],
},
},
ResourcesMap: map[string]*schema.Resource{
"ironic_node_v1": resourceNodeV1(),
"ironic_port_v1": resourcePortV1(),
"ironic_node_v1": resourceNodeV1(),
"ironic_port_v1": resourcePortV1(),
"ironic_allocation_v1": resourceAllocationV1(),
},
ConfigureFunc: configureProvider,
}
Expand Down
2 changes: 2 additions & 0 deletions ironic/provider_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// +build acceptance

package ironic

import (
Expand Down
160 changes: 160 additions & 0 deletions ironic/resource_ironic_allocation_v1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package ironic

import (
"fmt"
"log"
"time"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/baremetal/v1/allocations"
"github.com/hashicorp/terraform/helper/schema"
)

// Schema resource definition for an Ironic allocation.
func resourceAllocationV1() *schema.Resource {
return &schema.Resource{
Create: resourceAllocationV1Create,
Read: resourceAllocationV1Read,
Delete: resourceAllocationV1Delete,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"resource_class": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"candidate_nodes": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
ForceNew: true,
},
"traits": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
ForceNew: true,
},
"extra": {
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},
"node_uuid": {
Type: schema.TypeString,
Computed: true,
},
"state": {
Type: schema.TypeString,
Computed: true,
},
"last_error": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

// Create an allocation, including driving Ironic's state machine
func resourceAllocationV1Create(d *schema.ResourceData, meta interface{}) error {
client := meta.(*gophercloud.ServiceClient)

result, err := allocations.Create(client, allocationSchemaToCreateOpts(d)).Extract()
if err != nil {
return err
}

d.SetId(result.UUID)

// Wait for state to change from allocating
var state string
timeout := 1 * time.Minute
checkInterval := 5 * time.Second


for resourceAllocationV1Read(d, meta) ; state == "allocating" || state == ""; resourceAllocationV1Read(d, meta) {
state = d.Get("state").(string)
log.Printf("[DEBUG] Requested allocation %s; current state is '%s'\n", d.Id(), state)

time.Sleep(checkInterval)
timeout -= checkInterval
if timeout < 0 {
return fmt.Errorf("timed out waiting for allocation")
}
}

if state == "error" {
err := d.Get("last_error").(string)
resourceAllocationV1Delete(d, meta)
d.SetId("")
return fmt.Errorf("error creating resource: %s", err)
}

return nil
}

// Read the allocation's data from Ironic
func resourceAllocationV1Read(d *schema.ResourceData, meta interface{}) error {
client := meta.(*gophercloud.ServiceClient)

result, err := allocations.Get(client, d.Id()).Extract()
if err != nil {
return err
}

d.Set("name", result.Name)
d.Set("resource_class", result.ResourceClass)
d.Set("candidate_nodes", result.CandidateNodes)
d.Set("traits", result.Traits)
d.Set("extra", result.Extra)
d.Set("node_uuid", result.NodeUUID)
d.Set("state", result.State)
d.Set("last_error", result.LastError)

return nil
}

// Delete an allocation from Ironic
func resourceAllocationV1Delete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*gophercloud.ServiceClient)
return allocations.Delete(client, d.Id()).ExtractErr()
}

func allocationSchemaToCreateOpts(d *schema.ResourceData) *allocations.CreateOpts {
candidateNodesRaw := d.Get("candidate_nodes").([]interface{})
traitsRaw := d.Get("traits").([]interface{})
extraRaw := d.Get("extra").(map[string]interface{})

candidateNodes := make([]string, len(candidateNodesRaw))
for i := range candidateNodesRaw {
candidateNodes[i] = candidateNodesRaw[i].(string)
}

traits := make([]string, len(traitsRaw))
for i := range traitsRaw {
traits[i] = traitsRaw[i].(string)
}

extra := make(map[string]string)
for k, v := range extraRaw {
extra[k] = v.(string)
}

return &allocations.CreateOpts{
Name: d.Get("name").(string),
ResourceClass: d.Get("resource_class").(string),
CandidateNodes: candidateNodes,
Traits: traits,
Extra: extra,
}
}
Loading

0 comments on commit ce997ae

Please sign in to comment.