blob: 46fc8e2b669fdd2f0c0400d199057727c616f984 [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
package cloudstack
import (
"errors"
"fmt"
"log"
"sort"
"strings"
"time"
"github.com/apache/cloudstack-go/v2/cloudstack"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceCloudStackHost() *schema.Resource {
return &schema.Resource{
Read: resourceCloudStackHostRead,
Update: resourceCloudStackHostUpdate,
Create: resourceCloudStackHostCreate,
Delete: resourceCloudStackHostDelete,
Schema: map[string]*schema.Schema{
"hypervisor": {
Type: schema.TypeString,
Required: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
validHypervisors := []string{"xenserver", "kvm", "vmware", "baremetal", "simulator"}
sort.Strings(validHypervisors)
if sort.SearchStrings(validHypervisors, v.(string)) >= len(validHypervisors) {
errors = append(errors, fmt.Errorf("%q must be one of %v", k, validHypervisors))
}
return
},
ForceNew: true,
},
"pod_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"url": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"zone_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"cluster_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{
"cluster_name",
},
},
"cluster_name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{
"cluster_id",
},
},
"host_tags": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"username": {
Type: schema.TypeString,
Optional: true,
},
"password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},
"prevent_destroy": {
Type: schema.TypeBool,
Description: "Prevent the host from being destroyed. This is useful when you want to avoid destroy the host in any change.",
Optional: true,
Default: false,
},
"force_destroy": {
Type: schema.TypeBool,
Description: "Force the host to be destroyed.",
Optional: true,
Default: false,
},
"allocation_state": {
Type: schema.TypeString,
Optional: true,
Default: "Enabled",
},
"state": {
Type: schema.TypeString,
Computed: true,
},
"resource_state": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Computed: true,
},
// Creating a host can fail if the instance is still being created and Cloudsack
// user is created during cloud-init. This timeout is used to wait for the host
// to be created and Cloudstack user to be available.
"create_timeout": {
Type: schema.TypeInt,
Description: "Timeout in seconds to wait for the host to be created.",
Optional: true,
Default: 300,
},
// Destroying a host will put it in Maintenance mode first. If the VMs are still
// being migrated, the host will be in state PrepareForMaintenance. This timeout
// is used to wait for the host to be in Maintenance state.
"destroy_timeout": {
Type: schema.TypeInt,
Description: "Timeout in seconds to wait for the host to be destroyed.",
Optional: true,
Default: 300,
},
},
}
}
func resourceCloudStackHostCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
hypervisor := d.Get("hypervisor").(string)
pod_id := d.Get("pod_id").(string)
url := d.Get("url").(string)
zone_id := d.Get("zone_id").(string)
p := cs.Host.NewAddHostParams(hypervisor, pod_id, url, zone_id)
if cluster_id, ok := d.GetOk("cluster_id"); ok {
p.SetClusterid(cluster_id.(string))
}
if cluster_name, ok := d.GetOk("cluster_name"); ok {
p.SetClustername(cluster_name.(string))
}
if host_tags, ok := d.GetOk("host_tags"); ok {
p.SetHosttags(host_tags.([]string))
}
if username, ok := d.GetOk("username"); ok {
p.SetUsername(username.(string))
}
if password, ok := d.GetOk("password"); ok {
p.SetPassword(password.(string))
}
timeout := time.After(time.Duration(d.Get("create_timeout").(int)) * time.Second)
tick := time.NewTicker(5 * time.Second)
var err error
var host *cloudstack.AddHostResponse
for {
select {
case <-timeout:
return fmt.Errorf("timeout waiting for Host to be created, with error: %s", err)
case <-tick.C:
log.Printf("[DEBUG] Trying to create host %s", d.Get("url").(string))
host, err = cs.Host.AddHost(p)
if err != nil {
log.Printf("[ERROR] Error creating host %s: %s. Will try again...", d.Get("url").(string), err)
continue
}
if host.Id != "" {
log.Printf("[DEBUG] Host %s successfully created", url)
d.SetId(host.Id)
return resourceCloudStackHostRead(d, meta)
}
}
}
}
func resourceCloudStackHostRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
log.Printf("[DEBUG] Retrieving Host %s", d.Get("url").(string))
h, count, err := cs.Host.GetHostByID(d.Id())
if err != nil {
if count == 0 {
log.Printf("[WARN] Host %s does no longer exist", d.Get("url").(string))
d.SetId("")
return nil
}
return err
}
d.SetId(h.Id)
fields := map[string]interface{}{
"hypervisor": h.Hypervisor,
"pod_id": h.Podid,
"zone_id": h.Zoneid,
"state": h.State,
"resource_state": h.Resourcestate,
"name": h.Name,
}
for k, v := range fields {
if err := d.Set(k, v); err != nil {
return err
}
}
if cluster_id := d.Get("cluster_id"); cluster_id != "" {
d.Set("cluster_id", h.Clusterid)
} else {
d.Set("cluster_name", h.Clustername)
}
if h.Hosttags != "" {
d.Set("host_tags", strings.Split(h.Hosttags, ","))
}
return nil
}
func resourceCloudStackHostUpdate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] Updating Host: %s", d.Id())
cs := meta.(*cloudstack.CloudStackClient)
p := cs.Host.NewUpdateHostParams(d.Id())
if d.HasChange("allocation_state") {
log.Printf("[DEBUG] Updating Host allocation state: %s", d.Id())
p.SetAllocationstate(d.Get("allocation_state").(string))
}
if d.HasChange("host_tags") {
log.Printf("[DEBUG] Updating Host tags: %s", d.Id())
p.SetHosttags(d.Get("host_tags").([]string))
}
return resourceCloudStackHostRead(d, meta)
}
func resourceCloudStackHostDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
if d.Get("prevent_destroy").(bool) {
log.Printf("[INFO] Skipping Host deletion: %s", d.Id())
return fmt.Errorf("host %s is marked to be protected from deletion", d.Id())
}
log.Printf("[INFO] Removing Host: %s", d.Id())
mm := cs.Host.NewPrepareHostForMaintenanceParams(d.Id())
_, err := cs.Host.PrepareHostForMaintenance(mm)
if err != nil {
return fmt.Errorf("error preparing Host for maintenance: %s", err)
}
timeout := time.After(time.Duration(d.Get("destroy_timeout").(int)) * time.Second)
tick := time.NewTicker(3 * time.Second)
for {
select {
case <-timeout:
return errors.New("timeout waiting for Host to enter Maintenance state")
case <-tick.C:
log.Printf("[DEBUG] Checking Host state: %s", d.Id())
err = resourceCloudStackHostRead(d, meta)
if err != nil {
return fmt.Errorf("error reading Host: %s", err)
}
if d.Get("resource_state").(string) == "Maintenance" || d.Get("resource_state").(string) == "Disconnected" {
log.Printf("[INFO] Deleting Host: %s", d.Id())
h := cs.Host.NewDeleteHostParams(d.Id())
_, err = cs.Host.DeleteHost(h)
if err != nil {
return fmt.Errorf("error deleting Host: %s", err)
}
return nil
}
}
}
}