Skip to content

Commit 830c61d

Browse files
authored
Merge pull request #53 from liquidweb/resize-update-reboot-expection
Use new resizePlan method to determine resize reboot expectation
2 parents fba5b62 + dc3f4ab commit 830c61d

File tree

3 files changed

+225
-100
lines changed

3 files changed

+225
-100
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
Copyright © LiquidWeb
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package cmd
17+
18+
import (
19+
"errors"
20+
"fmt"
21+
"os"
22+
23+
"github.com/spf13/cobra"
24+
25+
"github.com/liquidweb/liquidweb-cli/types/api"
26+
"github.com/liquidweb/liquidweb-cli/utils"
27+
"github.com/liquidweb/liquidweb-cli/validate"
28+
)
29+
30+
var cloudServerResizeExpectationCmd = &cobra.Command{
31+
Use: "resize-expectation",
32+
Short: "Determine if a Cloud Server can be resized without downtime",
33+
Long: `This command can be used to determine if a Cloud Server can be resized to the requested
34+
config-id without downtime.
35+
36+
Depending on inventory and desired config-id (configuration) the resize could either
37+
require a reboot to complete, or be performed entirely live. The intention of this
38+
command is to provide the user with a sane expectation ahead of making the resize
39+
request.
40+
41+
If there is no inventory available, an exception will be raised.
42+
43+
Its important to note, this command will *not* make any changes to your Cloud Server.
44+
This command is purely for information gathering.
45+
`,
46+
Run: func(cmd *cobra.Command, args []string) {
47+
uniqIdFlag, _ := cmd.Flags().GetString("uniq-id")
48+
privateParentFlag, _ := cmd.Flags().GetString("private-parent")
49+
diskFlag, _ := cmd.Flags().GetInt64("diskspace")
50+
memoryFlag, _ := cmd.Flags().GetInt64("memory")
51+
vcpuFlag, _ := cmd.Flags().GetInt64("vcpu")
52+
configIdFlag, _ := cmd.Flags().GetInt64("config-id")
53+
54+
validateFields := map[interface{}]interface{}{
55+
uniqIdFlag: "UniqId",
56+
}
57+
58+
if err := validate.Validate(validateFields); err != nil {
59+
lwCliInst.Die(err)
60+
}
61+
62+
if privateParentFlag != "" && configIdFlag != -1 {
63+
lwCliInst.Die(errors.New("cant pass both --config-id and --private-parent flags"))
64+
}
65+
if privateParentFlag == "" && configIdFlag == -1 {
66+
lwCliInst.Die(errors.New("must pass --config-id or --private-parent"))
67+
}
68+
69+
apiArgs := map[string]interface{}{
70+
"uniq_id": uniqIdFlag,
71+
"config_id": configIdFlag,
72+
}
73+
74+
// if private parent, add args
75+
if privateParentFlag != "" {
76+
if memoryFlag <= 0 && diskFlag <= 0 && vcpuFlag <= 0 {
77+
lwCliInst.Die(errors.New("when --private-parent , at least one of --memory --disk --vcpu are required"))
78+
}
79+
80+
privateParentUniqId, _, err := lwCliInst.DerivePrivateParentUniqId(privateParentFlag)
81+
if err != nil {
82+
lwCliInst.Die(err)
83+
}
84+
85+
var cloudServerDetails apiTypes.CloudServerDetails
86+
if err = lwCliInst.CallLwApiInto(
87+
"bleed/storm/server/details",
88+
map[string]interface{}{
89+
"uniq_id": uniqIdFlag,
90+
}, &cloudServerDetails); err != nil {
91+
lwCliInst.Die(err)
92+
}
93+
94+
apiArgs["config_id"] = 0
95+
apiArgs["private_parent"] = privateParentUniqId
96+
apiArgs["disk"] = cloudServerDetails.DiskSpace
97+
apiArgs["memory"] = cloudServerDetails.Memory
98+
apiArgs["vcpu"] = cloudServerDetails.Vcpu
99+
100+
if diskFlag > 0 {
101+
apiArgs["disk"] = diskFlag
102+
}
103+
if vcpuFlag > 0 {
104+
apiArgs["vcpu"] = vcpuFlag
105+
}
106+
if memoryFlag > 0 {
107+
apiArgs["memory"] = memoryFlag
108+
}
109+
}
110+
111+
var expectation apiTypes.CloudServerResizeExpectation
112+
err := lwCliInst.CallLwApiInto("bleed/storm/server/resizePlan", apiArgs, &expectation)
113+
if err != nil {
114+
utils.PrintRed("Configuration Not Available\n\n")
115+
fmt.Printf("%s\n", err)
116+
os.Exit(1)
117+
}
118+
119+
utils.PrintGreen("Configuration Available\n\n")
120+
121+
fmt.Print("Resource Changes: Disk [")
122+
if expectation.DiskDifference == 0 {
123+
fmt.Printf("%d] ", expectation.DiskDifference)
124+
} else if expectation.DiskDifference >= 0 {
125+
utils.PrintGreen("%d", expectation.DiskDifference)
126+
fmt.Print("] ")
127+
} else {
128+
utils.PrintRed("%d", expectation.DiskDifference)
129+
fmt.Print("] ")
130+
}
131+
132+
fmt.Print("Memory [")
133+
if expectation.MemoryDifference == 0 {
134+
fmt.Printf("%d] ", expectation.MemoryDifference)
135+
} else if expectation.MemoryDifference >= 0 {
136+
utils.PrintGreen("%d", expectation.MemoryDifference)
137+
fmt.Print("] ")
138+
} else {
139+
utils.PrintRed("%d", expectation.MemoryDifference)
140+
fmt.Print("] ")
141+
}
142+
143+
fmt.Print("Vcpu [")
144+
if expectation.VcpuDifference == 0 {
145+
fmt.Printf("%d]\n", expectation.VcpuDifference)
146+
} else if expectation.VcpuDifference >= 0 {
147+
utils.PrintGreen("%d", expectation.VcpuDifference)
148+
fmt.Print("]\n")
149+
} else {
150+
utils.PrintRed("%d", expectation.VcpuDifference)
151+
fmt.Print("]\n")
152+
}
153+
154+
if expectation.RebootRequired {
155+
utils.PrintYellow("\nReboot required.\n")
156+
} else {
157+
utils.PrintGreen("\nNo reboot required.\n")
158+
}
159+
},
160+
}
161+
162+
func init() {
163+
cloudServerCmd.AddCommand(cloudServerResizeExpectationCmd)
164+
165+
cloudServerResizeExpectationCmd.Flags().String("uniq-id", "", "uniq-id of Cloud Server")
166+
167+
cloudServerResizeExpectationCmd.Flags().String("private-parent", "",
168+
"name or uniq-id of the Private Parent (see: 'cloud private-parent list')")
169+
cloudServerResizeExpectationCmd.Flags().Int64("diskspace", -1, "diskspace for the Cloud Server (when private-parent)")
170+
cloudServerResizeExpectationCmd.Flags().Int64("memory", -1, "memory for the Cloud Server (when private-parent)")
171+
cloudServerResizeExpectationCmd.Flags().Int64("vcpu", -1, "vcpus for the Cloud Server (when private-parent)")
172+
173+
cloudServerResizeExpectationCmd.Flags().Int64("config-id", -1,
174+
"config-id to check availability for (when !private-parent) (see: 'cloud server options --configs')")
175+
176+
if err := cloudServerResizeExpectationCmd.MarkFlagRequired("uniq-id"); err != nil {
177+
lwCliInst.Die(err)
178+
}
179+
}

instance/cloudServerResize.go

Lines changed: 23 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ func (self *Client) CloudServerResize(params *CloudServerResizeParams) (result s
7373
return
7474
}
7575

76+
resizePlanArgs := map[string]interface{}{
77+
"uniq_id": params.UniqId,
78+
}
79+
7680
resizeArgs := map[string]interface{}{
7781
"uniq_id": params.UniqId,
7882
"skip_fs_resize": skipFsResizeInt,
@@ -89,10 +93,6 @@ func (self *Client) CloudServerResize(params *CloudServerResizeParams) (result s
8993
return
9094
}
9195

92-
var (
93-
liveResize bool
94-
twoRebootResize bool
95-
)
9696
if params.PrivateParent == "" {
9797
// non private parent resize
9898
if params.Memory != -1 || params.DiskSpace != -1 || params.Vcpu != -1 {
@@ -107,27 +107,8 @@ func (self *Client) CloudServerResize(params *CloudServerResizeParams) (result s
107107
}
108108

109109
validateFields[params.ConfigId] = "PositiveInt64"
110-
if err = validate.Validate(validateFields); err != nil {
111-
return
112-
}
113110

114-
// determine reboot expectation.
115-
// resize up full: 2 reboot
116-
// resize up quick (skip-fs-resize) 1 reboot
117-
// resize down: 1 reboot
118-
var configDetails apiTypes.CloudConfigDetails
119-
if err = self.CallLwApiInto("bleed/storm/config/details",
120-
map[string]interface{}{"id": params.ConfigId}, &configDetails); err != nil {
121-
return
122-
}
123-
124-
if configDetails.Disk >= cloudServerDetails.DiskSpace {
125-
// disk space going up..
126-
if !params.SkipFsResize {
127-
// .. and not skipping fs resize, will be 2 reboots.
128-
twoRebootResize = true
129-
}
130-
}
111+
resizePlanArgs["config_id"] = params.ConfigId
131112
} else {
132113
// private parent resize specific logic
133114
if params.Memory == -1 && params.DiskSpace == -1 && params.Vcpu == -1 {
@@ -141,37 +122,6 @@ func (self *Client) CloudServerResize(params *CloudServerResizeParams) (result s
141122
return
142123
}
143124

144-
var (
145-
diskspaceChanging bool
146-
vcpuChanging bool
147-
memoryChanging bool
148-
memoryCanLive bool
149-
vcpuCanLive bool
150-
)
151-
// record what resources are changing
152-
if params.DiskSpace != -1 {
153-
if cloudServerDetails.DiskSpace != params.DiskSpace {
154-
diskspaceChanging = true
155-
}
156-
}
157-
if params.Vcpu != -1 {
158-
if cloudServerDetails.Vcpu != params.Vcpu {
159-
vcpuChanging = true
160-
}
161-
}
162-
if params.Memory != -1 {
163-
if cloudServerDetails.Memory != params.Memory {
164-
memoryChanging = true
165-
}
166-
}
167-
// allow resizes to a private parent even if its old non private parent config had exact same specs
168-
if cloudServerDetails.ConfigId == 0 && cloudServerDetails.PrivateParent != privateParentUniqId {
169-
if !diskspaceChanging && !vcpuChanging && !memoryChanging {
170-
err = errors.New("private parent resize, but passed diskspace, memory, vcpu values match existing values")
171-
return
172-
}
173-
}
174-
175125
resizeArgs["newsize"] = 0 // 0 indicates private parent resize
176126
resizeArgs["parent"] = privateParentUniqId // uniq_id of the private parent
177127
validateFields[privateParentUniqId] = "UniqId"
@@ -194,64 +144,37 @@ func (self *Client) CloudServerResize(params *CloudServerResizeParams) (result s
194144
validateFields[params.Vcpu] = "PositiveInt64"
195145
}
196146

197-
// determine if this will be a live resize
198-
if _, exists := resizeArgs["memory"]; exists {
199-
if params.Memory >= cloudServerDetails.Memory {
200-
// asking for more RAM
201-
memoryCanLive = true
202-
}
203-
}
204-
if _, exists := resizeArgs["vcpu"]; exists {
205-
if params.Vcpu >= cloudServerDetails.Vcpu {
206-
// asking for more vcpu
207-
vcpuCanLive = true
208-
}
209-
}
210-
211-
if params.Memory != -1 && params.Vcpu != -1 {
212-
if vcpuCanLive && memoryCanLive {
213-
liveResize = true
214-
}
215-
} else if memoryCanLive {
216-
liveResize = true
217-
} else if vcpuCanLive {
218-
liveResize = true
219-
}
220-
221-
// if diskspace allocation changes its not currently ever done live regardless of memory, vcpu
222-
if params.DiskSpace != -1 {
223-
if resizeArgs["diskspace"] != cloudServerDetails.DiskSpace {
224-
liveResize = false
225-
}
226-
}
147+
resizePlanArgs["config_id"] = 0
148+
resizePlanArgs["private_parent"] = privateParentUniqId
149+
resizePlanArgs["memory"] = resizeArgs["memory"]
150+
resizePlanArgs["disk"] = resizeArgs["diskspace"]
151+
resizePlanArgs["vcpu"] = resizeArgs["vcpu"]
227152
}
228153

229154
if err = validate.Validate(validateFields); err != nil {
230155
return
231156
}
232157

158+
var expectation apiTypes.CloudServerResizeExpectation
159+
if err = self.CallLwApiInto("bleed/storm/server/resizePlan", resizePlanArgs, &expectation); err != nil {
160+
err = fmt.Errorf("Configuration Not Available\n\n%s\n", err)
161+
return
162+
}
163+
233164
if _, err = self.LwCliApiClient.Call("bleed/server/resize", resizeArgs); err != nil {
234165
return
235166
}
236167

237168
var b bytes.Buffer
238-
b.WriteString(fmt.Sprintf("server resized started! You can check progress with 'cloud server status --uniq-id %s'\n\n", params.UniqId))
239169

240-
if liveResize {
241-
b.WriteString(fmt.Sprintf("\nthis resize will be performed live without downtime.\n"))
242-
} else {
243-
rebootExpectation := "one reboot"
244-
if twoRebootResize {
245-
rebootExpectation = "two reboots"
246-
}
247-
b.WriteString(fmt.Sprintf(
248-
"\nexpect %s during this process. Your server will be online as the disk is copied to the destination.\n",
249-
rebootExpectation))
170+
b.WriteString(fmt.Sprintf("Server resized started! You can check progress with 'cloud server status --uniq-id %s'\n\n", params.UniqId))
171+
b.WriteString(fmt.Sprintf("Resource changes: Memory [%d] Disk [%d] Vcpu [%d]\n", expectation.MemoryDifference,
172+
expectation.DiskDifference, expectation.VcpuDifference))
250173

251-
if twoRebootResize {
252-
b.WriteString(fmt.Sprintf(
253-
"\tTIP: Avoid the second reboot by passing --skip-fs-resize. See usage for additional details.\n"))
254-
}
174+
if expectation.RebootRequired {
175+
b.WriteString("\nExpect a reboot during this resize.\n")
176+
} else {
177+
b.WriteString("\nThis resize will be performed live without downtime.\n")
255178
}
256179

257180
result = b.String()

types/api/cloud.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,29 @@ type CloudBlockStorageVolumeResize struct {
490490
UniqId string `json:"uniq_id" mapstructure:"uniq_id"`
491491
}
492492

493+
type CloudServerResizeExpectation struct {
494+
DiskDifference int64 `json:"diskDifference" mapstructure:"diskDifference"`
495+
MemoryDifference int64 `json:"memoryDifference" mapstructure:"memoryDifference"`
496+
VcpuDifference int64 `json:"vcpuDifference" mapstructure:"vcpuDifference"`
497+
RebootRequired FlexBool `json:"rebootRequired" mapstructure:"rebootRequired"`
498+
}
499+
500+
type FlexBool bool
501+
502+
func (self *FlexBool) UnmarshalJSON(data []byte) error {
503+
str := string(data)
504+
505+
if str == "1" || str == "true" {
506+
*self = true
507+
} else if str == "0" || str == "false" {
508+
*self = false
509+
} else {
510+
return fmt.Errorf("Boolean unmarshal error: invalid input %s", str)
511+
}
512+
513+
return nil
514+
}
515+
493516
type CloudObjectStoreDetails struct {
494517
Accnt int64 `json:"accnt" mapstructure:"accnt"`
495518
Caps []CloudObjectStoreDetailsCapsEntry `json:"caps" mapstructure:"caps"`

0 commit comments

Comments
 (0)