mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
221 lines
6.9 KiB
Go
221 lines
6.9 KiB
Go
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package nodecmd
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
nodePkg "github.com/luxfi/cli/v2/pkg/node"
|
|
|
|
"github.com/luxfi/cli/v2/pkg/ansible"
|
|
awsAPI "github.com/luxfi/cli/v2/pkg/cloud/aws"
|
|
gcpAPI "github.com/luxfi/cli/v2/pkg/cloud/gcp"
|
|
"github.com/luxfi/cli/v2/pkg/cobrautils"
|
|
"github.com/luxfi/cli/v2/pkg/constants"
|
|
"github.com/luxfi/cli/v2/pkg/models"
|
|
"github.com/luxfi/cli/v2/pkg/ssh"
|
|
"github.com/luxfi/cli/v2/pkg/utils"
|
|
"github.com/luxfi/cli/v2/pkg/ux"
|
|
"github.com/spf13/cobra"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
var diskSize string
|
|
|
|
func newResizeCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "resize [clusterName]",
|
|
Short: "(ALPHA Warning) Resize cluster node and disk sizes",
|
|
Long: `(ALPHA Warning) This command is currently in experimental mode.
|
|
|
|
The node resize command can change the amount of CPU, memory and disk space available for the cluster nodes.
|
|
`,
|
|
Args: cobrautils.MinimumNArgs(1),
|
|
RunE: resize,
|
|
}
|
|
cmd.Flags().StringVar(&nodeType, "node-type", "", "Node type to resize (e.g. t3.2xlarge)")
|
|
cmd.Flags().StringVar(&diskSize, "disk-size", "", "Disk size to resize in Gb (e.g. 1000Gb)")
|
|
cmd.Flags().StringVar(&awsProfile, "aws-profile", constants.AWSDefaultCredential, "aws profile to use")
|
|
return cmd
|
|
}
|
|
|
|
func preResizeChecks(clusterName string) error {
|
|
if nodeType == "" && diskSize == "" {
|
|
return fmt.Errorf("at least one of the flags --node-type or --disk-size must be provided")
|
|
}
|
|
if diskSize != "" && !strings.HasSuffix(diskSize, "Gb") {
|
|
return fmt.Errorf("disk-size must be in Gb")
|
|
}
|
|
if diskSize != "" {
|
|
diskSizeGb := strings.TrimSuffix(diskSize, "Gb")
|
|
if _, err := strconv.Atoi(diskSizeGb); err != nil {
|
|
return fmt.Errorf("disk-size must be an integer")
|
|
}
|
|
}
|
|
if err := failForExternal(clusterName); err != nil {
|
|
return fmt.Errorf("cannot resize external cluster %s", clusterName)
|
|
}
|
|
clusterConfig, err := app.GetClusterConfig(clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if clusterConfig.Local {
|
|
return notImplementedForLocal("resize")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resize(_ *cobra.Command, args []string) error {
|
|
clusterName := args[0]
|
|
if err := nodePkg.CheckCluster(app, clusterName); err != nil {
|
|
return err
|
|
}
|
|
if err := preResizeChecks(clusterName); err != nil {
|
|
return err
|
|
}
|
|
clusterNodes, err := nodePkg.GetClusterNodes(app, clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
monitoringNode, err := getClusterMonitoringNode(clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nodesToResize := utils.Filter(clusterNodes, func(node string) bool {
|
|
return node != monitoringNode
|
|
})
|
|
|
|
if nodeType != "" {
|
|
ux.Logger.PrintLineSeparator()
|
|
ux.Logger.PrintToUser("Node performance may be impacted during resizing")
|
|
ux.Logger.PrintToUser("Please note that instances will be restarted during resizing.")
|
|
ux.Logger.PrintToUser("This operation may take some time to complete. Thank you for your patience.")
|
|
}
|
|
|
|
if diskSize != "" {
|
|
ux.Logger.PrintLineSeparator()
|
|
ux.Logger.PrintToUser("Disk performance may be impacted during resizing")
|
|
ux.Logger.PrintToUser("Please ensure that the cluster is not under heavy load.")
|
|
}
|
|
|
|
for _, node := range nodesToResize {
|
|
nodeConfig, err := app.LoadClusterNodeConfig(node)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hostAnsibleID, err := models.HostCloudIDToAnsibleID(nodeConfig.CloudService, nodeConfig.NodeID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
host, err := ansible.GetHostByNodeID(hostAnsibleID, app.GetAnsibleInventoryDirPath(clusterName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !(authorizeAccess || nodePkg.AuthorizedAccessFromSettings(app)) && (requestCloudAuth(nodeConfig.CloudService) != nil) {
|
|
return fmt.Errorf("cloud access is required")
|
|
}
|
|
spinSession := ux.NewUserSpinner()
|
|
// resize node and disk. If error occurs, log it and continue to next host
|
|
if nodeType != "" {
|
|
spinner := spinSession.SpinToUser(utils.ScriptLog(nodeConfig.NodeID, "Resizing Instance Type"))
|
|
if err := resizeNode(nodeConfig); err != nil {
|
|
ux.SpinFailWithError(spinner, "", err)
|
|
} else {
|
|
ux.SpinComplete(spinner)
|
|
}
|
|
}
|
|
if diskSize != "" {
|
|
spinner := spinSession.SpinToUser(utils.ScriptLog(nodeConfig.NodeID, "Resizing Disk"))
|
|
diskSizeGb, _ := strconv.Atoi(strings.TrimSuffix(diskSize, "Gb"))
|
|
if err := resizeDisk(nodeConfig, diskSizeGb); err != nil {
|
|
ux.SpinFailWithError(spinner, "", err)
|
|
} else if err := ssh.RunSSHUpsizeRootDisk(host); err != nil {
|
|
ux.SpinFailWithError(spinner, "", err)
|
|
} else {
|
|
ux.SpinComplete(spinner)
|
|
}
|
|
}
|
|
spinSession.Stop()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// resizeDisk resizes the disk size of the node
|
|
func resizeDisk(nodeConfig models.NodeConfig, diskSize int) error {
|
|
if diskSize > math.MaxInt32 {
|
|
return fmt.Errorf("disk size exceeds maximum supported value")
|
|
}
|
|
switch nodeConfig.CloudService {
|
|
case "", constants.AWSCloudService:
|
|
ec2Svc, err := awsAPI.NewAwsCloud(awsProfile, nodeConfig.Region)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rootVolume, err := ec2Svc.GetRootVolumeID(nodeConfig.NodeID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ec2Svc.ResizeVolume(rootVolume, int32(diskSize))
|
|
case constants.GCPCloudService:
|
|
gcpClient, projectName, _, err := getGCPCloudCredentials()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gcpCloud, err := gcpAPI.NewGcpCloud(gcpClient, projectName, context.Background())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rootVolume, err := gcpCloud.GetRootVolumeID(nodeConfig.NodeID, nodeConfig.Region)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if diskSize > math.MaxInt {
|
|
return fmt.Errorf("disk size exceeds maximum supported value")
|
|
}
|
|
return gcpCloud.ResizeVolume(rootVolume, nodeConfig.Region, int64(diskSize))
|
|
default:
|
|
return fmt.Errorf("cloud service %s is not supported", nodeConfig.CloudService)
|
|
}
|
|
}
|
|
|
|
// resizeNode changes the node type of the instance
|
|
func resizeNode(nodeConfig models.NodeConfig) error {
|
|
switch nodeConfig.CloudService {
|
|
case "", constants.AWSCloudService:
|
|
ec2Svc, err := awsAPI.NewAwsCloud(awsProfile, nodeConfig.Region)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
isSupported, err := ec2Svc.IsInstanceTypeSupported(nodeType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isSupported {
|
|
return fmt.Errorf("instance type %s is not supported", nodeType)
|
|
}
|
|
return ec2Svc.ChangeInstanceType(nodeConfig.NodeID, nodeType)
|
|
case constants.GCPCloudService:
|
|
gcpClient, projectName, _, err := getGCPCloudCredentials()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gcpCloud, err := gcpAPI.NewGcpCloud(gcpClient, projectName, context.Background())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
isSupported, err := gcpCloud.IsInstanceTypeSupported(nodeType, nodeConfig.Region)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isSupported {
|
|
return fmt.Errorf("instance type %s is not supported", nodeType)
|
|
}
|
|
return gcpCloud.ChangeInstanceType(nodeConfig.NodeID, nodeConfig.Region, nodeType)
|
|
default:
|
|
return fmt.Errorf("cloud service %s is not supported", nodeConfig.CloudService)
|
|
}
|
|
}
|