Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add update method of VM resource #7

Merged
merged 3 commits into from
Nov 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 174 additions & 21 deletions anxcloud/resource_virtual_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@ package anxcloud
import (
"context"
"fmt"
"log"
"time"

"github.com/anexia-it/go-anxcloud/pkg/client"
"github.com/anexia-it/go-anxcloud/pkg/vsphere"
"github.com/anexia-it/go-anxcloud/pkg/vsphere/provisioning/vm"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
maxDNSEntries = 4
vmPoweredOn = "poweredOn"
vmPoweredOff = "poweredOff"
)

func resourceVirtualServer() *schema.Resource {
Expand All @@ -26,12 +29,60 @@ func resourceVirtualServer() *schema.Resource {
UpdateContext: resourceVirtualServerUpdate,
DeleteContext: resourceVirtualServerDelete,
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(15 * time.Minute),
Create: schema.DefaultTimeout(60 * time.Minute),
Read: schema.DefaultTimeout(1 * time.Minute),
Update: schema.DefaultTimeout(5 * time.Minute),
Update: schema.DefaultTimeout(60 * time.Minute),
Delete: schema.DefaultTimeout(5 * time.Minute),
},
Schema: schemaVirtualServer(),
CustomizeDiff: customdiff.All(
customdiff.ForceNewIf("network", func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool {
old, new := d.GetChange("network")
oldNets := expandVirtualServerNetworks(old.([]interface{}))
newNets := expandVirtualServerNetworks(new.([]interface{}))

if len(oldNets) > len(newNets) {
return true
}

for i, n := range oldNets {
if n.VLAN != newNets[i].VLAN {
key := fmt.Sprintf("network.%d.vlan_id", i)
if err := d.ForceNew(key); err != nil {
log.Fatalf("[ERROR] unable to force new '%s': %v", key, err)
}
}

if n.NICType != newNets[i].NICType {
key := fmt.Sprintf("network.%d.nic_type", i)
if err := d.ForceNew(key); err != nil {
log.Fatalf("[ERROR] unable to force new '%s': %v", key, err)
}
}

if len(n.IPs) != len(newNets[i].IPs) {
key := fmt.Sprintf("network.%d.ips", i)
if err := d.ForceNew(key); err != nil {
log.Fatalf("[ERROR] unable to force new '%s': %v", key, err)
}
}

for j, ip := range n.IPs {
if ip != newNets[i].IPs[j] {
key := fmt.Sprintf("network.%d.ips", i)
if err := d.ForceNew(key); err != nil {
log.Fatalf("[ERROR] unable to force new '%s': %v", key, err)
}
}
}
}

return false
}),
customdiff.ForceNewIfChange("disk", func(ctx context.Context, old, new, meta interface{}) bool {
return old.(int) > new.(int)
}),
),
}
}

Expand Down Expand Up @@ -148,6 +199,56 @@ func resourceVirtualServerRead(ctx context.Context, d *schema.ResourceData, m in
return diag.FromErr(err)
}

// we miss information about:
// * cpu_performance_type
// * networks.ips - we have info endpoint, but it's not compatible with networks.ips.identifiers
// * networks.nic_type - we have info endpoint, but it does not return networks.nic_type

if err = d.Set("cpus", info.CPU); err != nil {
diags = append(diags, diag.FromErr(err)...)
}
if v := d.Get("sockets").(int); v != 0 {
// info.Cores should be info.Sockets, there is info.Cpus which is info.Cores
if err = d.Set("sockets", info.Cores); err != nil {
diags = append(diags, diag.FromErr(err)...)
}
}

if err = d.Set("memory", info.RAM); err != nil {
diags = append(diags, diag.FromErr(err)...)
}

if len(info.DiskInfo) != 1 {
return diag.Errorf("unsupported number of disks, currently only 1 disk is allowed, got %d", len(info.DiskInfo))
}
if err = d.Set("disk", info.DiskInfo[0].DiskGB); err != nil {
diags = append(diags, diag.FromErr(err)...)
}
if v := d.Get("disk_type").(string); v != "" {
if err = d.Set("disk_type", info.DiskInfo[0].DiskType); err != nil {
diags = append(diags, diag.FromErr(err)...)
}
}

// networks status is taken from info endpoint and there is no id that we can join
// vm.Networks with info.Networks thus we must trust that order from the info endpoint is correct
var networks []vm.Network
specNetworks := expandVirtualServerNetworks(d.Get("network").([]interface{}))
for i, n := range info.Network {
network := vm.Network{
VLAN: n.VLAN,
// we miss information about nic_type and ips and we have to prevent resource recreation
// that's why we copy the following fields
NICType: specNetworks[i].NICType,
IPs: specNetworks[i].IPs,
}
networks = append(networks, network)
}
fNetworks := flattenVirtualServerNetwork(networks)
if err = d.Set("network", fNetworks); err != nil {
diags = append(diags, diag.FromErr(err)...)
}

fInfo := flattenVirtualServerInfo(&info)
if err = d.Set("info", fInfo); err != nil {
diags = append(diags, diag.FromErr(err)...)
Expand All @@ -157,27 +258,79 @@ func resourceVirtualServerRead(ctx context.Context, d *schema.ResourceData, m in
}

func resourceVirtualServerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
c := m.(client.Client)
v := vsphere.NewAPI(c)
ch := vm.Change{
Reboot: d.Get("force_restart_if_needed").(bool),
EnableDangerous: d.Get("critical_operation_confirmed").(bool),
}

//c := m.(client.Client)
//v := vsphere.NewAPI(c)

/*
1. Network
1.1 add new ok
1.2 remove old ForceNew
2. Disk (it doesn't make sense since I cannot add multiple disks at start)
2.1 list to add
2.2 list to remove
3.3 list to change
3. cpu_performance_type
4. sockets
5. memory_mb
6. cpus
*/
if d.HasChanges("sockets", "memory", "cpus") {
ch.CPUs = d.Get("cpus").(int)
ch.CPUSockets = d.Get("sockets").(int)
ch.MemoryMBs = d.Get("memory").(int)
}

if len(diags) > 0 {
return diags
// must stay in a separate condition as any endpoint doesn't return info about the current state
// thus we lose control over expected and current states
if d.HasChange("cpu_performance_type") {
ch.CPUPerformanceType = d.Get("cpu_performance_type").(string)
}

if d.HasChange("network") {
old, new := d.GetChange("network")
oldNets := expandVirtualServerNetworks(old.([]interface{}))
newNets := expandVirtualServerNetworks(new.([]interface{}))

if len(oldNets) < len(newNets) {
ch.AddNICs = newNets[len(oldNets):]
} else {
return diag.Errorf(
"unsupported update operation, cannot remove network or update its parameters",
)
}
}

if d.HasChanges("disk_type", "disk") {
var disk vm.Disk

info := expandVirtualServerInfo(d.Get("info").([]interface{}))
if len(info.DiskInfo) != 1 {
return diag.Errorf("unsupported number of disks, currently only 1 disk is allowed, got %d", len(info.DiskInfo))
}

disk.ID = info.DiskInfo[0].DiskID
disk.Type = d.Get("disk_type").(string)
disk.SizeGBs = d.Get("disk").(int)

ch.ChangeDisks = append(ch.ChangeDisks, disk)
}

if _, err := v.Provisioning().VM().Update(ctx, d.Id(), ch); err != nil {
return diag.FromErr(err)
}

vmState := resource.StateChangeConf{
Delay: 3 * time.Minute,
Timeout: d.Timeout(schema.TimeoutUpdate),
MinTimeout: 10 * time.Second,
Pending: []string{
vmPoweredOff,
},
Target: []string{
vmPoweredOn,
},
Refresh: func() (interface{}, string, error) {
info, err := v.Info().Get(ctx, d.Id())
if err != nil {
return "", "", err
}
return info, info.Status, nil
},
}
_, err := vmState.WaitForStateContext(ctx)
if err != nil {
return diag.FromErr(err)
}

return resourceVirtualServerRead(ctx, d, m)
Expand Down
Loading