diff --git a/CHANGELOG.md b/CHANGELOG.md index e9fba064..e508e66b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ BUG FIXES: * library/virtual_environment_nodes: Fix node IP address format +* resource/virtual_environment_vm: Fix VM ID collision when `vm_id` is not specified WORKAROUNDS: diff --git a/go.sum b/go.sum index 3fb5391e..e94fd28d 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= diff --git a/proxmox/common_types.go b/proxmox/common_types.go index 873fe89a..d92fcdbf 100644 --- a/proxmox/common_types.go +++ b/proxmox/common_types.go @@ -18,6 +18,9 @@ type CustomBool bool // CustomCommaSeparatedList allows a JSON string to also be a string array. type CustomCommaSeparatedList []string +// CustomInt allows a JSON integer value to also be a string. +type CustomInt int + // CustomLineBreakSeparatedList allows a multiline JSON string to also be a string array. type CustomLineBreakSeparatedList []string @@ -70,6 +73,25 @@ func (r *CustomCommaSeparatedList) UnmarshalJSON(b []byte) error { return nil } +// UnmarshalJSON converts a JSON value to an integer. +func (r *CustomInt) UnmarshalJSON(b []byte) error { + s := string(b) + + if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") { + s = s[1 : len(s)-1] + } + + i, err := strconv.ParseInt(s, 10, 32) + + if err != nil { + return err + } + + *r = CustomInt(i) + + return nil +} + // MarshalJSON converts a boolean to a JSON value. func (r *CustomLineBreakSeparatedList) MarshalJSON() ([]byte, error) { s := strings.Join(*r, "\n") diff --git a/proxmox/virtual_environment_cluster.go b/proxmox/virtual_environment_cluster.go new file mode 100644 index 00000000..ec9567b2 --- /dev/null +++ b/proxmox/virtual_environment_cluster.go @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package proxmox + +import ( + "errors" +) + +// GetClusterNextID retrieves the next free VM identifier for the cluster. +func (c *VirtualEnvironmentClient) GetClusterNextID(vmID *int) (*int, error) { + reqBody := &VirtualEnvironmentClusterNextIDRequestBody{ + VMID: vmID, + } + + resBody := &VirtualEnvironmentClusterNextIDResponseBody{} + err := c.DoRequest(hmGET, "cluster/nextid", reqBody, resBody) + + if err != nil { + return nil, err + } + + if resBody.Data == nil { + return nil, errors.New("The server did not include a data object in the response") + } + + return (*int)(resBody.Data), nil +} diff --git a/proxmox/virtual_environment_cluster_types.go b/proxmox/virtual_environment_cluster_types.go new file mode 100644 index 00000000..a3e49b10 --- /dev/null +++ b/proxmox/virtual_environment_cluster_types.go @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package proxmox + +// VirtualEnvironmentClusterNextIDRequestBody contains the data for a cluster next id request. +type VirtualEnvironmentClusterNextIDRequestBody struct { + VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"` +} + +// VirtualEnvironmentClusterNextIDResponseBody contains the body from a cluster next id response. +type VirtualEnvironmentClusterNextIDResponseBody struct { + Data *CustomInt `json:"data,omitempty"` +} diff --git a/proxmox/virtual_environment_vm.go b/proxmox/virtual_environment_vm.go index a9e3032e..cff74794 100644 --- a/proxmox/virtual_environment_vm.go +++ b/proxmox/virtual_environment_vm.go @@ -7,11 +7,22 @@ package proxmox import ( "errors" "fmt" + "log" "net/url" "strings" + "sync" "time" ) +const ( + getVMIDStep = 1 +) + +var ( + getVMIDCounter = -1 + getVMIDCounterMutex = &sync.Mutex{} +) + // CloneVM clones a virtual machine. func (c *VirtualEnvironmentClient) CloneVM(nodeName string, vmID int, d *VirtualEnvironmentVMCloneRequestBody) error { return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/qemu/%d/clone", url.PathEscape(nodeName), vmID), d, nil) @@ -45,30 +56,46 @@ func (c *VirtualEnvironmentClient) GetVM(nodeName string, vmID int) (*VirtualEnv // GetVMID retrieves the next available VM identifier. func (c *VirtualEnvironmentClient) GetVMID() (*int, error) { - nodes, err := c.ListNodes() + getVMIDCounterMutex.Lock() + defer getVMIDCounterMutex.Unlock() - if err != nil { - return nil, err + if getVMIDCounter < 0 { + nextVMID, err := c.GetClusterNextID(nil) + + if err != nil { + return nil, err + } + + if nextVMID == nil { + return nil, errors.New("Unable to retrieve the next available VM identifier") + } + + getVMIDCounter = *nextVMID + getVMIDStep + + log.Printf("[DEBUG] Determined next available VM identifier to be %d", *nextVMID) + + return nextVMID, nil } - vmID := 100 + vmID := getVMIDCounter -VMID: for vmID <= 2147483637 { - for _, n := range nodes { - err := c.DoRequest(hmGET, fmt.Sprintf("nodes/%s/qemu/%d/status/current", url.PathEscape(n.Name), vmID), nil, nil) + _, err := c.GetClusterNextID(&vmID) - if err == nil { - vmID += 5 + if err != nil { + vmID += getVMIDStep - continue VMID - } + continue } + getVMIDCounter = vmID + getVMIDStep + + log.Printf("[DEBUG] Determined next available VM identifier to be %d", vmID) + return &vmID, nil } - return nil, errors.New("Unable to retrieve the next available VM identifier") + return nil, errors.New("Unable to determine the next available VM identifier") } // GetVMNetworkInterfacesFromAgent retrieves the network interfaces reported by the QEMU agent. diff --git a/proxmoxtf/utils.go b/proxmoxtf/utils.go index 3cc197cd..c755b9b9 100644 --- a/proxmoxtf/utils.go +++ b/proxmoxtf/utils.go @@ -349,7 +349,7 @@ func getVLANIDsValidator() schema.SchemaValidateFunc { func getVMIDValidator() schema.SchemaValidateFunc { return func(i interface{}, k string) (ws []string, es []error) { - min := 1 + min := 100 max := 2147483647 v, ok := i.(int)