From 333a9390a7649a40846782a54f70fb7fc8e8fd61 Mon Sep 17 00:00:00 2001 From: "mark.wei" Date: Mon, 13 Jul 2026 18:24:14 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E6=B7=BB=E5=8A=A0uhadoop-cli=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E5=91=BD=E4=BB=A4=E5=92=8C=E6=B5=8B=E8=AF=95=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/products.gen.go | 2 + products/uhadoop/TEST.md | 414 ++++++++++++++++++ products/uhadoop/internal/uhadoop/add_node.go | 54 +++ products/uhadoop/internal/uhadoop/cmd.go | 28 ++ products/uhadoop/internal/uhadoop/create.go | 303 +++++++++++++ products/uhadoop/internal/uhadoop/delete.go | 41 ++ products/uhadoop/internal/uhadoop/describe.go | 86 ++++ products/uhadoop/internal/uhadoop/list.go | 203 +++++++++ .../internal/uhadoop/list_framework_app.go | 112 +++++ .../internal/uhadoop/list_node_type.go | 75 ++++ .../internal/uhadoop/restart_service.go | 51 +++ products/uhadoop/internal/uhadoop/rows.go | 59 +++ .../uhadoop/internal/uhadoop/upgrade_disk.go | 45 ++ .../uhadoop/internal/uhadoop/upgrade_node.go | 44 ++ products/uhadoop/product.go | 21 + products/uhadoop/product.yaml | 6 + products/uhadoop/testdata/cmdtree.golden | 92 ++++ products/uhadoop/testdata/completion.golden | 38 ++ 18 files changed, 1674 insertions(+) create mode 100644 products/uhadoop/TEST.md create mode 100644 products/uhadoop/internal/uhadoop/add_node.go create mode 100644 products/uhadoop/internal/uhadoop/cmd.go create mode 100644 products/uhadoop/internal/uhadoop/create.go create mode 100644 products/uhadoop/internal/uhadoop/delete.go create mode 100644 products/uhadoop/internal/uhadoop/describe.go create mode 100644 products/uhadoop/internal/uhadoop/list.go create mode 100644 products/uhadoop/internal/uhadoop/list_framework_app.go create mode 100644 products/uhadoop/internal/uhadoop/list_node_type.go create mode 100644 products/uhadoop/internal/uhadoop/restart_service.go create mode 100644 products/uhadoop/internal/uhadoop/rows.go create mode 100644 products/uhadoop/internal/uhadoop/upgrade_disk.go create mode 100644 products/uhadoop/internal/uhadoop/upgrade_node.go create mode 100644 products/uhadoop/product.go create mode 100644 products/uhadoop/product.yaml create mode 100644 products/uhadoop/testdata/cmdtree.golden create mode 100644 products/uhadoop/testdata/completion.golden diff --git a/cmd/products.gen.go b/cmd/products.gen.go index 8a02dabd04..0ccb7ecf83 100644 --- a/cmd/products.gen.go +++ b/cmd/products.gen.go @@ -17,6 +17,7 @@ import ( "github.com/ucloud/ucloud-cli/products/udisk" "github.com/ucloud/ucloud-cli/products/udpn" "github.com/ucloud/ucloud-cli/products/ufs" + "github.com/ucloud/ucloud-cli/products/uhadoop" "github.com/ucloud/ucloud-cli/products/uhost" "github.com/ucloud/ucloud-cli/products/ulb" "github.com/ucloud/ucloud-cli/products/umodelverse" @@ -44,6 +45,7 @@ func registeredProducts() []cli.Product { udisk.New(), udpn.New(), ufs.New(), + uhadoop.New(), uhost.New(), ulb.New(), umodelverse.New(), diff --git a/products/uhadoop/TEST.md b/products/uhadoop/TEST.md new file mode 100644 index 0000000000..6ee6bbf6cd --- /dev/null +++ b/products/uhadoop/TEST.md @@ -0,0 +1,414 @@ +## UHadoop CLI 测试清单 + +### 准备工作 + +```bash +# 1. 确认凭据已配置 +ucloud config list + +# 2. 查看可用地域 +ucloud region + +# 3. 设置 GOROOT 使用正确的 Go 工具链(g use 1.25.12 后需要) +export GOROOT=/Users/user/.g/go GOTOOLCHAIN=local + +# 4. 记录测试用的 region 和 zone,后续命令中替换 +# 示例: --region cn-bj2 --zone cn-bj2-02 +``` + +> **编译/运行命令前缀**: 后续所有 `go run/build/test` 命令需加 `GOROOT=/Users/user/.g/go GOTOOLCHAIN=local` + +### 测试用参数 + +| 参数 | 示例值 | 说明 | +|------|--------|------| +| `` | `cn-bj2` | 地域 | +| `` | `cn-bj2-02` | 可用区 | +| `` | `uhadoop-xxxxx` | 已有集群 ID | +| `` | `uvpc-xxxxx` | VPC ID(可选,不传则自动发现) | +| `` | `subnet-xxxxx` | 子网 ID(可选,不传则自动发现) | + +--- + +### 一、只读命令测试 + +#### 1.1 list-framework-app — 查询框架和应用 + +```bash +# help +go run . uhadoop list-framework-app --help + +# table 输出 +go run . uhadoop list-framework-app --region --zone + +# JSON 输出 +go run . uhadoop list-framework-app --region --zone --output json +``` + +**验证点**:返回表格含 Framework、FrameworkVersion、ReleaseVersion、UseCase、Apps、Versions、MustHas 列。 + +--- + +#### 1.2 list-node-type — 查询可用机型 + +```bash +# help +go run . uhadoop list-node-type --help + +# 全部机型 +go run . uhadoop list-node-type --region --zone + +# 按框架过滤 +go run . uhadoop list-node-type \ + --region --zone \ + --framework Hadoop --framework-version 3.3.4-udh3.2 + +# 按角色过滤 +go run . uhadoop list-node-type \ + --region --zone \ + --node-role master + +# JSON 输出 +go run . uhadoop list-node-type --region --zone --output json +``` + +**验证点**:返回表格含 NodeType、HostType、CPU、Memory、SuitableRole、IsUsable、GpuType、GpuCount、DiskMinSize、DiskMaxSize 列。 + +--- + +#### 1.3 list — 列出集群 + +```bash +# help +go run . uhadoop list --help + +# 基本查询 +go run . uhadoop list --region + +# 按 zone 过滤 +go run . uhadoop list --region --zone + +# 分页 +go run . uhadoop list --region --limit 5 --offset 0 + +# 只输出 ID +go run . uhadoop list --region --id-only + +# JSON 输出 +go run . uhadoop list --region --output json + +# 跨地域 +go run . uhadoop list --all-region --output json +``` + +**验证点**:表格含 InstanceId、InstanceName、Framework、ReleaseVersion、HadoopVersion、State、Zone、CreateTime、ExpireTime 列。 + +--- + +#### 1.4 describe — 查询集群详情 + +```bash +# help +go run . uhadoop describe --help + +# 查询(替换 为上一步 list 返回的 ID) +go run . uhadoop describe --region --zone + +# JSON 输出 +go run . uhadoop describe --region --zone --output json +``` + +**验证点**:返回 ClusterSet 数组中包含 InstanceId、NodeSet、AppConfigSet 等详情。 + +--- + +### 二、修改类命令测试(谨慎执行) + +> ⚠️ 以下命令会对真实资源产生修改,请在明确后果后执行。 + +#### 2.1 restart-service — 启停服务 + +```bash +# help +go run . uhadoop restart-service --help + +# 重启服务 +go run . uhadoop restart-service \ + --region --zone \ + --instance-id \ + --service-name Hive + +# 只停止 +go run . uhadoop restart-service \ + --region --zone \ + --instance-id \ + --service-name Hive --only-stop + +# 只启动 +go run . uhadoop restart-service \ + --region --zone \ + --instance-id \ + --service-name Hive --only-start + +# 指定节点重启 +go run . uhadoop restart-service \ + --region --zone \ + --instance-id \ + --service-name Hive \ + --node-role core --node-role task +``` + +**验证点**:返回 `{"RetCode": 0, "State": "running"}`。 + +--- + +#### 2.2 add-node — 扩容节点 + +```bash +# help +go run . uhadoop add-node --help + +# Core 节点扩容(磁盘参数有默认值,可省略) +go run . uhadoop add-node \ + --region --zone \ + --instance-id \ + --node-role core \ + --node-type o.hadoop2m.xlarge \ + --node-count 1 + +# Task 节点扩容 +go run . uhadoop add-node \ + --region --zone \ + --instance-id \ + --node-role task \ + --node-type o.hadoop2m.xlarge \ + --node-count 1 + +# Client 节点扩容(需要密码,明文即可,自动 base64 编码) +go run . uhadoop add-node \ + --region --zone \ + --instance-id \ + --node-role client \ + --node-type o.hadoop2m.medium \ + --node-count 1 \ + --password 'YourPass!' +``` + +**验证点**:返回 `{"RetCode": 0}`。 + +--- + +#### 2.3 upgrade-node — 升级节点机型 + +```bash +# help +go run . uhadoop upgrade-node --help + +# Master 升级(不需要 --node-name) +go run . uhadoop upgrade-node \ + --region --zone \ + --instance-id \ + --node-role master \ + --node-type o.hadoop2m.xlarge + +# Core/Task 升级(需要 --node-name,从 describe 返回的 NodeSet 中获取) +go run . uhadoop upgrade-node \ + --region --zone \ + --instance-id \ + --node-role core \ + --node-type o.hadoop2m.2xlarge \ + --node-name -core1 +``` + +**验证点**:返回 `{"RetCode": 0}`。 + +--- + +#### 2.4 upgrade-disk — 扩容磁盘 + +```bash +# help +go run . uhadoop upgrade-disk --help + +# Core 节点扩容数据盘到 500G +go run . uhadoop upgrade-disk \ + --region --zone \ + --instance-id \ + --node-role core \ + --data-disk-size 500 \ + --node-name -core1 + +# Master 节点扩容(不需要 --node-name) +go run . uhadoop upgrade-disk \ + --region --zone \ + --instance-id \ + --node-role master \ + --data-disk-size 500 \ + --boot-disk-size 100 +``` + +**验证点**:返回 `{"RetCode": 0}`。 + +--- + +#### 2.5 create — 创建集群 + +```bash +# help +go run . uhadoop create --help + +# Hadoop 集群 — 使用 Spark 模板(推荐方式) +go run . uhadoop create \ + --region --zone \ + --name test-uhadoop-cli \ + --framework Hadoop \ + --framework-version 3.3.4-udh3.2 \ + --password 'YourPass123!' \ + --cluster-case Spark \ + --master-node-type o.hadoop2m.xlarge \ + --core-node-type o.hadoop2m.xlarge + +# Hadoop 集群 — Hbase 场景 +go run . uhadoop create \ + --region --zone \ + --name test-hbase-cli \ + --framework Hadoop \ + --framework-version 3.3.4-udh3.2 \ + --password 'YourPass123!' \ + --cluster-case Hbase \ + --master-node-type o.hadoop2m.xlarge \ + --core-node-type o.hadoop2m.xlarge + +# Hadoop 集群 — Core Hadoop 场景 +go run . uhadoop create \ + --region --zone \ + --name test-core-cli \ + --framework Hadoop \ + --framework-version 3.3.4-udh3.2 \ + --password 'YourPass123!' \ + --cluster-case Core-Hadoop \ + --master-node-type o.hadoop2m.xlarge \ + --core-node-type o.hadoop2m.xlarge + +# Hadoop 集群 — 手动指定 app-config(不使用模板) +go run . uhadoop create \ + --region --zone \ + --name test-manual-cli \ + --framework Hadoop \ + --framework-version 3.3.4-udh3.2 \ + --password 'YourPass123!' \ + --master-node-type o.hadoop2m.xlarge \ + --core-node-type o.hadoop2m.xlarge \ + --app-config Spark#3.5.3 \ + --app-config Hive#3.1.3 \ + --app-config Zookeeper#3.8.4 \ + --app-config Hue#4.11.0 \ + --app-config Hdfs#3.3.4 + +# MR 集群(需要 --storage-cluster-id,含 Task 节点) +go run . uhadoop create \ + --region --zone \ + --name test-mr-cli \ + --framework MR \ + --framework-version 3.3.4-udh3.2 \ + --password 'YourPass123!' \ + --storage-cluster-id \ + --cluster-case Spark \ + --master-node-type o.hadoop2m.xlarge \ + --core-node-type o.hadoop2m.xlarge \ + --task-node-type o.hadoop2m.xlarge --task-count 2 + +# StarRocks 集群(--cluster-case 不适用,手动指定 app-config) +go run . uhadoop create \ + --region --zone \ + --name test-sr-cli \ + --framework StarRocks-Shared-Data \ + --framework-version 3.3.4-udh3.2 \ + --password 'YourPass123!' \ + --master-node-type o.hadoop2m.xlarge \ + --task-node-type o.hadoop2m.xlarge --task-count 1 \ + --app-config StarRocks#4.1.1 +``` + +**验证点**:返回 `{"RetCode": 0, "InstanceId": "uhadoop-xxxxx"}`。 + +--- + +#### 2.6 delete — 删除集群 + +```bash +# help +go run . uhadoop delete --help + +# 删除集群(不释放 EIP) +go run . uhadoop delete \ + --region --zone + +# 删除集群并释放 EIP +go run . uhadoop delete \ + --region --zone \ + --release-eip +``` + +**验证点**:返回 `{"RetCode": 0}`。 + +--- + +### 三、自动化校验 + +```bash +# 编译 +GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go build ./... + +# 运行 uhadoop 模块的 golden 测试 +GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go test ./hack/snapshot -v -run 'ProductGoldens/uhadoop' +GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go test ./hack/snapshot -v -run 'ProductCompletionGoldens/uhadoop' + +# 运行全部 snapshot 测试 +GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go test ./hack/snapshot -v +``` + +--- + +### 四、--cluster-case 模板参考 + +| framework-version | Spark | Hbase | Core-Hadoop | +| --- | --- | --- | --- | +| `3.3.4-udh3.2` | Spark#3.5.3, Hive#3.1.3, Hue#4.11.0, Zookeeper#3.8.4, Mysql#8.0.32, Yarn#3.3.4 | Hbase#2.4.18, Hue#4.11.0, Zookeeper#3.8.4, Mysql#8.0.32, Yarn#3.3.4, Phoenix#5.2.1 | Hive#3.1.3, Hue#4.11.0, Zookeeper#3.8.4, Yarn#3.3.4 | +| `3.3.4-udh3.1` | Spark#3.5.3, Hive#3.1.3, Hue#4.11.0, Zookeeper#3.8.4, Mysql#8.0.32, Yarn#3.3.4 | Hbase#2.4.18, Hue#4.11.0, Zookeeper#3.8.4, Mysql#8.0.32, Yarn#3.3.4, Phoenix#5.2.1 | Hive#3.1.3, Hue#4.11.0, Zookeeper#3.8.4, Yarn#3.3.4 | +| `3.2.1-udh3.0` | Spark#3.3.0, Hive#3.1.3, Hue#4.7.1, Zookeeper#3.6.3, Mysql#5.6.47, Yarn#3.2.1 | Hbase#2.2.4, Hue#4.7.1, Zookeeper#3.6.3, Mysql#5.6.47, Yarn#3.2.1, Phoenix#5.1.2 | Hive#3.1.3, Hue#4.7.1, Zookeeper#3.6.3, Yarn#3.2.1 | +| `2.8.5-udh2.2` | Spark#2.4.6, Hive#2.3.6, Hue#4.7.1, Zookeeper#3.4.13, Mysql#5.6.47, Yarn#2.8.5 | Hbase#1.4.10, Hue#4.7.1, Zookeeper#3.4.13, Mysql#5.6.47, Yarn#2.8.5, Phoenix#4.14.3 | Hive#2.3.6, Hue#4.7.1, Zookeeper#3.4.13, Yarn#2.8.5 | +| `2.6.0-cdh5.13.3` | Spark#2.4.3, Hive#2.3.3, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0 | Hbase#1.2.0, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0, Phoenix#4.14.0 | Hive#2.3.3, Hue#3.10.0, Zookeeper#3.4.5, Yarn#2.6.0 | +| `2.6.0-cdh5.4.9` | Spark#2.0.1, Hive#1.2.1, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0 | Hbase#1.0.0, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0, Phoenix#4.6.0 | Hive#1.2.1, Hue#3.10.0, Zookeeper#3.4.5, Yarn#2.6.0 | + +> Hadoop 框架自动追加 Hdfs 组件(版本号按上表推)。 + +--- + +### 测试结果记录 + +| # | 命令 | 状态 | 备注 | +|---|------|------|------| +| 1 | `list-framework-app --help` | ⬜ | | +| 2 | `list-framework-app ` | ⬜ | | +| 3 | `list-node-type --help` | ⬜ | | +| 4 | `list-node-type ` | ⬜ | | +| 5 | `list --help` | ⬜ | | +| 6 | `list ` | ⬜ | | +| 7 | `describe --help` | ⬜ | | +| 8 | `describe ` | ⬜ | | +| 9 | `restart-service --help` | ⬜ | | +| 10 | `restart-service ` | ⬜ | | +| 11 | `add-node --help` | ⬜ | | +| 12 | `add-node ` | ⬜ | | +| 13 | `upgrade-node --help` | ⬜ | | +| 14 | `upgrade-disk --help` | ⬜ | | +| 15 | `create --help` | ⬜ | | +| 16 | `create --cluster-case Spark` | ⬜ | | +| 17 | `create --cluster-case Hbase` | ⬜ | | +| 18 | `create --cluster-case Core-Hadoop` | ⬜ | | +| 19 | `delete --help` | ⬜ | | +| 20 | `go build ./...` | ⬜ | | +| 21 | `go test ./hack/snapshot -v` | ⬜ | | + +> ⬜ = 待测试   ✅ = 通过   ❌ = 失败 diff --git a/products/uhadoop/internal/uhadoop/add_node.go b/products/uhadoop/internal/uhadoop/add_node.go new file mode 100644 index 0000000000..af755d237f --- /dev/null +++ b/products/uhadoop/internal/uhadoop/add_node.go @@ -0,0 +1,54 @@ +package uhadoop + +import ( + "encoding/base64" + + "github.com/spf13/cobra" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newAddNode ucloud uhadoop add-node +func newAddNode(ctx *cli.Context) *cobra.Command { + var rawPassword string + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + req := client.NewAddUHadoopInstanceNodeRequest() + cmd := &cobra.Command{ + Use: "add-node", + Short: "Add nodes to a UHadoop cluster", + Long: `Add a number of nodes to an existing UHadoop cluster`, + Run: func(cmd *cobra.Command, args []string) { + if rawPassword != "" { + req.Password = sdkStr(base64.StdEncoding.EncodeToString([]byte(rawPassword))) + } + resp, err := client.AddUHadoopInstanceNode(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.PrintJSON(resp) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + req.InstanceId = cmd.Flags().String("instance-id", "", "Required. Cluster instance ID") + req.NodeRole = cmd.Flags().String("node-role", "", "Required. Node role: core|task|client") + req.NodeType = cmd.Flags().String("node-type", "", "Required. Node type, e.g. o.hadoop2m.medium (from list-node-type)") + req.NodeCount = cmd.Flags().Int("node-count", 1, "Optional. Number of nodes to add, default 1") + cmd.Flags().StringVar(&rawPassword, "password", "", "Optional. Login password, plain text (NodeRole=client is required)") + req.BootDiskSize = cmd.Flags().String("boot-disk-size", "50", "Optional. Boot disk size in GB for new-type nodes, default 50") + req.BootDiskType = cmd.Flags().String("boot-disk-type", "CLOUD_RSSD", "Optional. Boot disk type for new-type nodes, default CLOUD_RSSD") + req.DataDiskSize = cmd.Flags().String("data-disk-size", "200", "Optional. Data disk size in GB for new-type nodes, default 200") + req.DataDiskNum = cmd.Flags().String("data-disk-num", "1", "Optional. Data disk number for new-type nodes, default 1") + req.DataDiskType = cmd.Flags().String("data-disk-type", "CLOUD_RSSD", "Optional. Data disk type for new-type nodes, default CLOUD_RSSD") + + command.SetFlagValues(cmd, "node-role", "core", "task", "client") + + return cmd +} diff --git a/products/uhadoop/internal/uhadoop/cmd.go b/products/uhadoop/internal/uhadoop/cmd.go new file mode 100644 index 0000000000..0694aaa552 --- /dev/null +++ b/products/uhadoop/internal/uhadoop/cmd.go @@ -0,0 +1,28 @@ +package uhadoop + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the top-level `uhadoop` command. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "uhadoop", + Short: "List,create,delete,describe UHadoop clusters and manage nodes and services", + Long: `List,create,delete,describe UHadoop clusters and manage nodes and services`, + Args: cobra.NoArgs, + } + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newDescribe(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newAddNode(ctx)) + cmd.AddCommand(newListNodeType(ctx)) + cmd.AddCommand(newListFrameworkApp(ctx)) + cmd.AddCommand(newRestartService(ctx)) + cmd.AddCommand(newUpgradeNode(ctx)) + cmd.AddCommand(newUpgradeDisk(ctx)) + return cmd +} diff --git a/products/uhadoop/internal/uhadoop/create.go b/products/uhadoop/internal/uhadoop/create.go new file mode 100644 index 0000000000..92bcadcf4d --- /dev/null +++ b/products/uhadoop/internal/uhadoop/create.go @@ -0,0 +1,303 @@ +package uhadoop + +import ( + "encoding/base64" + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + "github.com/ucloud/ucloud-sdk-go/ucloud/response" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// instanceGroupConfig matches the real API's InstanceGroupConfigs struct. +type instanceGroupConfig struct { + NodeRole string `json:"NodeRole"` + NodeType string `json:"NodeType"` + Count int `json:"Count"` + DataDiskSize int `json:"DataDiskSize"` + DataDiskNum int `json:"DataDiskNum"` + DataDiskType string `json:"DataDiskType"` + BootDiskSize int `json:"BootDiskSize"` + BootDiskType string `json:"BootDiskType"` +} + +// createRequest is the real request body for CreateUHadoopInstance. +type createRequest struct { + request.CommonBase + InstanceName string `json:"InstanceName"` + Framework string `json:"Framework"` + FrameworkVersion string `json:"FrameworkVersion"` + Password string `json:"Password"` + VPCId string `json:"VPCId"` + SubnetId string `json:"SubnetId"` + ChargeType string `json:"ChargeType,omitempty"` + Quantity int `json:"Quantity,omitempty"` + BusinessId string `json:"BusinessId,omitempty"` + StorgeClusterId string `json:"StorgeClusterId,omitempty"` + StandAloneMetaStore string `json:"StandAloneMetaStore,omitempty"` + IsSecurityEnabled string `json:"IsSecurityEnabled,omitempty"` + SecGroupIds string `json:"SecGroupIds,omitempty"` + US3Bucket string `json:"US3Bucket,omitempty"` + US3AccessKey string `json:"US3AccessKey,omitempty"` + US3SecretKey string `json:"US3SecretKey,omitempty"` + US3TokenName string `json:"US3TokenName,omitempty"` + AppConfigs []string `json:"AppConfigs"` + InstanceGroupConfigs []instanceGroupConfig `json:"InstanceGroupConfigs"` +} + +// createResponse is the response for CreateUHadoopInstance. +type createResponse struct { + response.CommonBase + InstanceId string `json:"InstanceId"` + Message string `json:"Message"` +} + +// clusterCaseApps maps (FrameworkVersion, ClusterCase) to app templates. +// "Hadoop" cluster cases: Spark, Hbase, Core Hadoop. +// For "Hadoop" framework, Hdfs is auto-appended. For "MR", it is not. +var clusterCaseApps = map[string]map[string][]string{ + "3.3.4-udh3.2": { + "Spark": {"Spark#3.5.3", "Hive#3.1.3", "Hue#4.11.0", "Zookeeper#3.8.4", "Mysql#8.0.32", "Yarn#3.3.4"}, + "Hbase": {"Hbase#2.4.18", "Hue#4.11.0", "Zookeeper#3.8.4", "Mysql#8.0.32", "Yarn#3.3.4", "Phoenix#5.2.1"}, + "Core-Hadoop": {"Hive#3.1.3", "Hue#4.11.0", "Zookeeper#3.8.4", "Yarn#3.3.4"}, + }, + "3.3.4-udh3.1": { + "Spark": {"Spark#3.5.3", "Hive#3.1.3", "Hue#4.11.0", "Zookeeper#3.8.4", "Mysql#8.0.32", "Yarn#3.3.4"}, + "Hbase": {"Hbase#2.4.18", "Hue#4.11.0", "Zookeeper#3.8.4", "Mysql#8.0.32", "Yarn#3.3.4", "Phoenix#5.2.1"}, + "Core-Hadoop": {"Hive#3.1.3", "Hue#4.11.0", "Zookeeper#3.8.4", "Yarn#3.3.4"}, + }, + "3.2.1-udh3.0": { + "Spark": {"Spark#3.3.0", "Hive#3.1.3", "Hue#4.7.1", "Zookeeper#3.6.3", "Mysql#5.6.47", "Yarn#3.2.1"}, + "Hbase": {"Hbase#2.2.4", "Hue#4.7.1", "Zookeeper#3.6.3", "Mysql#5.6.47", "Yarn#3.2.1", "Phoenix#5.1.2"}, + "Core-Hadoop": {"Hive#3.1.3", "Hue#4.7.1", "Zookeeper#3.6.3", "Yarn#3.2.1"}, + }, + "2.8.5-udh2.2": { + "Spark": {"Spark#2.4.6", "Hive#2.3.6", "Hue#4.7.1", "Zookeeper#3.4.13", "Mysql#5.6.47", "Yarn#2.8.5"}, + "Hbase": {"Hbase#1.4.10", "Hue#4.7.1", "Zookeeper#3.4.13", "Mysql#5.6.47", "Yarn#2.8.5", "Phoenix#4.14.3"}, + "Core-Hadoop": {"Hive#2.3.6", "Hue#4.7.1", "Zookeeper#3.4.13", "Yarn#2.8.5"}, + }, + "2.6.0-cdh5.13.3": { + "Spark": {"Spark#2.4.3", "Hive#2.3.3", "Hue#3.10.0", "Zookeeper#3.4.5", "Mysql#5.1.73", "Yarn#2.6.0"}, + "Hbase": {"Hbase#1.2.0", "Hue#3.10.0", "Zookeeper#3.4.5", "Mysql#5.1.73", "Yarn#2.6.0", "Phoenix#4.14.0"}, + "Core-Hadoop": {"Hive#2.3.3", "Hue#3.10.0", "Zookeeper#3.4.5", "Yarn#2.6.0"}, + }, + "2.6.0-cdh5.4.9": { + "Spark": {"Spark#2.0.1", "Hive#1.2.1", "Hue#3.10.0", "Zookeeper#3.4.5", "Mysql#5.1.73", "Yarn#2.6.0"}, + "Hbase": {"Hbase#1.0.0", "Hue#3.10.0", "Zookeeper#3.4.5", "Mysql#5.1.73", "Yarn#2.6.0", "Phoenix#4.6.0"}, + "Core-Hadoop": {"Hive#1.2.1", "Hue#3.10.0", "Zookeeper#3.4.5", "Yarn#2.6.0"}, + }, +} + +// hdfsVersionByFramework maps framework-version to the Hdfs app version for Hadoop framework. +var hdfsVersionByFramework = map[string]string{ + "3.3.4-udh3.2": "Hdfs#3.3.4", + "3.3.4-udh3.1": "Hdfs#3.3.4", + "3.2.1-udh3.0": "Hdfs#3.2.1", + "2.8.5-udh2.2": "Hdfs#2.8.5", + "2.6.0-cdh5.13.3": "Hdfs#2.6.0", + "2.6.0-cdh5.4.9": "Hdfs#2.6.0", +} + +// newCreate ucloud uhadoop create +func newCreate(ctx *cli.Context) *cobra.Command { + var ( + rawPassword string + clusterCase string + master instanceGroupConfig + core instanceGroupConfig + task instanceGroupConfig + ) + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + sdkReq := client.NewCreateUHadoopInstanceRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a UHadoop cluster", + Long: `Create a UHadoop cluster with specified configuration`, + Run: func(cmd *cobra.Command, args []string) { + if sdkReq.Framework != nil && (*sdkReq.Framework == "StarRocks-Shared-Nothing" || *sdkReq.Framework == "StarRocks-Shared-Data") { + if !cmd.Flags().Changed("master-count") { + master.Count = 3 + } + } + groups := buildGroups(master, core, task) + if len(groups) == 0 { + ctx.HandleError(fmt.Errorf("at least one node group (--master-node-type or --core-node-type) is required")) + return + } + + // Resolve app configs: --cluster-case overrides --app-config + appConfigs := sdkReq.AppConfigs + if clusterCase != "" { + if sdkReq.FrameworkVersion == nil || *sdkReq.FrameworkVersion == "" { + ctx.HandleError(fmt.Errorf("--framework-version is required with --cluster-case")) + return + } + versionMap, ok := clusterCaseApps[*sdkReq.FrameworkVersion] + if !ok { + ctx.HandleError(fmt.Errorf("unsupported framework-version %q for --cluster-case, supported: %v", + *sdkReq.FrameworkVersion, supportedVersions())) + return + } + template, ok := versionMap[clusterCase] + if !ok { + ctx.HandleError(fmt.Errorf("no cluster-case %q for framework-version %q", clusterCase, *sdkReq.FrameworkVersion)) + return + } + appConfigs = append([]string(nil), template...) + + // For Hadoop framework, auto-append Hdfs + if sdkReq.Framework != nil && *sdkReq.Framework == "Hadoop" { + if hdfsVer, ok := hdfsVersionByFramework[*sdkReq.FrameworkVersion]; ok { + appConfigs = append(appConfigs, hdfsVer) + } + } + } + + req := &createRequest{ + InstanceName: *sdkReq.InstanceName, + Framework: *sdkReq.Framework, + FrameworkVersion: *sdkReq.FrameworkVersion, + Password: base64.StdEncoding.EncodeToString([]byte(rawPassword)), + VPCId: *sdkReq.VPCId, + SubnetId: *sdkReq.SubnetId, + AppConfigs: appConfigs, + InstanceGroupConfigs: groups, + } + req.Region = sdkReq.Region + req.Zone = sdkReq.Zone + req.ProjectId = sdkReq.ProjectId + if sdkReq.ChargeType != nil { + req.ChargeType = *sdkReq.ChargeType + } + if sdkReq.Quantity != nil { + req.Quantity = *sdkReq.Quantity + } + if sdkReq.BusinessId != nil { + req.BusinessId = *sdkReq.BusinessId + } + if sdkReq.StorgeClusterId != nil { + req.StorgeClusterId = *sdkReq.StorgeClusterId + } + if sdkReq.StandAloneMetaStore != nil { + req.StandAloneMetaStore = *sdkReq.StandAloneMetaStore + } + if sdkReq.IsSecurityEnabled != nil { + req.IsSecurityEnabled = *sdkReq.IsSecurityEnabled + } + if sdkReq.SecGroupIds != nil { + req.SecGroupIds = *sdkReq.SecGroupIds + } + if sdkReq.US3Bucket != nil { + req.US3Bucket = *sdkReq.US3Bucket + } + if sdkReq.US3AccessKey != nil { + req.US3AccessKey = *sdkReq.US3AccessKey + } + if sdkReq.US3SecretKey != nil { + req.US3SecretKey = *sdkReq.US3SecretKey + } + if sdkReq.US3TokenName != nil { + req.US3TokenName = *sdkReq.US3TokenName + } + + var resp createResponse + err := client.InvokeAction("CreateUHadoopInstance", req, &resp) + if err != nil { + ctx.HandleError(err) + return + } + ctx.PrintJSON(resp) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindRegion(cmd, sdkReq) + ctx.BindZone(cmd, sdkReq) + ctx.BindProjectID(cmd, sdkReq) + sdkReq.InstanceName = cmd.Flags().String("name", "", "Required. Instance name") + sdkReq.Framework = cmd.Flags().String("framework", "", "Required. Framework: Hadoop|HDFS|MR|StarRocks-Shared-Nothing|StarRocks-Shared-Data") + sdkReq.FrameworkVersion = cmd.Flags().String("framework-version", "", "Required. Framework version, e.g. 3.3.4-udh3.2") + cmd.Flags().StringVar(&rawPassword, "password", "", "Required. Login password (plain text, auto base64 encoded)") + sdkReq.VPCId = cmd.Flags().String("vpc-id", "", "Optional. VPC ID, auto-discovered if not set") + sdkReq.SubnetId = cmd.Flags().String("subnet-id", "", "Optional. Subnet ID, auto-discovered if not set") + sdkReq.ChargeType = cmd.Flags().String("charge-type", "Month", "Optional. Charge type: Month|Year|Dynamic") + sdkReq.Quantity = cmd.Flags().Int("quantity", 1, "Optional. Quantity for charge type") + sdkReq.BusinessId = cmd.Flags().String("business-id", "Default", "Optional. Business group ID") + sdkReq.StorgeClusterId = cmd.Flags().String("storage-cluster-id", "", "Optional. Storage cluster ID for MR framework") + sdkReq.StandAloneMetaStore = cmd.Flags().String("meta-store", "", "Optional. Stand alone meta store type, e.g. 'udb'") + sdkReq.IsSecurityEnabled = cmd.Flags().String("security-enabled", "", "Optional. Enable security group: true|false") + sdkReq.SecGroupIds = cmd.Flags().String("sec-group-ids", "", "Optional. Security group IDs, comma separated") + sdkReq.US3Bucket = cmd.Flags().String("us3-bucket", "", "Optional. US3 bucket name for StarRocks-Shared-Data") + sdkReq.US3AccessKey = cmd.Flags().String("us3-access-key", "", "Optional. US3 access key for StarRocks-Shared-Data") + sdkReq.US3SecretKey = cmd.Flags().String("us3-secret-key", "", "Optional. US3 secret key for StarRocks-Shared-Data") + sdkReq.US3TokenName = cmd.Flags().String("us3-token-name", "", "Optional. US3 token name for StarRocks-Shared-Data") + + // Cluster case flag — when set, auto-fills app-config based on the template + cmd.Flags().StringVar(&clusterCase, "cluster-case", "", "Optional. Cluster use case: Spark|Hbase|Core-Hadoop. When set, --app-config is auto-filled from the template") + + // Master node group + master.NodeRole = "master" + cmd.Flags().StringVar(&master.NodeType, "master-node-type", "o.hadoop2m.xlarge", "Master node type, default o.hadoop2m.xlarge") + cmd.Flags().IntVar(&master.Count, "master-count", 2, "Master node count, default 2") + cmd.Flags().IntVar(&master.DataDiskSize, "master-data-disk-size", 100, "Master data disk size in GB, default 100") + cmd.Flags().IntVar(&master.DataDiskNum, "master-data-disk-num", 1, "Master data disk number, default 1") + cmd.Flags().StringVar(&master.DataDiskType, "master-data-disk-type", "CLOUD_RSSD", "Master data disk type, default CLOUD_RSSD") + cmd.Flags().IntVar(&master.BootDiskSize, "master-boot-disk-size", 50, "Master boot disk size in GB, default 50") + cmd.Flags().StringVar(&master.BootDiskType, "master-boot-disk-type", "CLOUD_RSSD", "Master boot disk type, default CLOUD_RSSD") + + // Core node group + core.NodeRole = "core" + cmd.Flags().StringVar(&core.NodeType, "core-node-type", "o.hadoop2m.xlarge", "Core node type, default o.hadoop2m.xlarge") + cmd.Flags().IntVar(&core.Count, "core-count", 3, "Core node count, default 3") + cmd.Flags().IntVar(&core.DataDiskSize, "core-data-disk-size", 200, "Core data disk size in GB, default 200") + cmd.Flags().IntVar(&core.DataDiskNum, "core-data-disk-num", 1, "Core data disk number, default 1") + cmd.Flags().StringVar(&core.DataDiskType, "core-data-disk-type", "CLOUD_RSSD", "Core data disk type, default CLOUD_RSSD") + cmd.Flags().IntVar(&core.BootDiskSize, "core-boot-disk-size", 50, "Core boot disk size in GB, default 50") + cmd.Flags().StringVar(&core.BootDiskType, "core-boot-disk-type", "CLOUD_RSSD", "Core boot disk type, default CLOUD_RSSD") + + // Task node group (optional) + task.NodeRole = "task" + cmd.Flags().StringVar(&task.NodeType, "task-node-type", "o.hadoop2m.xlarge", "Optional. Task node type, default o.hadoop2m.xlarge") + cmd.Flags().IntVar(&task.Count, "task-count", 0, "Task node count, default 0 (no task nodes)") + cmd.Flags().IntVar(&task.DataDiskSize, "task-data-disk-size", 200, "Task data disk size in GB, default 200") + cmd.Flags().IntVar(&task.DataDiskNum, "task-data-disk-num", 1, "Task data disk number, default 1") + cmd.Flags().StringVar(&task.DataDiskType, "task-data-disk-type", "CLOUD_RSSD", "Task data disk type, default CLOUD_RSSD") + cmd.Flags().IntVar(&task.BootDiskSize, "task-boot-disk-size", 50, "Task boot disk size in GB, default 50") + cmd.Flags().StringVar(&task.BootDiskType, "task-boot-disk-type", "CLOUD_RSSD", "Task boot disk type, default CLOUD_RSSD") + + cmd.Flags().StringSliceVar(&sdkReq.AppConfigs, "app-config", nil, "App configs in format App#Version. Ignored when --cluster-case is set") + + command.SetFlagValues(cmd, "cluster-case", "Spark", "Hbase", "Core-Hadoop") + command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic") + command.SetFlagValues(cmd, "security-enabled", "true", "false") + + return cmd +} + +func supportedVersions() []string { + vs := make([]string, 0, len(clusterCaseApps)) + for v := range clusterCaseApps { + vs = append(vs, v) + } + return vs +} + +func buildGroups(master, core, task instanceGroupConfig) []instanceGroupConfig { + var groups []instanceGroupConfig + if master.NodeType != "" { + groups = append(groups, master) + } + if core.NodeType != "" { + groups = append(groups, core) + } + if task.NodeType != "" && task.Count > 0 { + groups = append(groups, task) + } + return groups +} diff --git a/products/uhadoop/internal/uhadoop/delete.go b/products/uhadoop/internal/uhadoop/delete.go new file mode 100644 index 0000000000..051ba5659f --- /dev/null +++ b/products/uhadoop/internal/uhadoop/delete.go @@ -0,0 +1,41 @@ +package uhadoop + +import ( + "github.com/spf13/cobra" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDelete ucloud uhadoop delete +func newDelete(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + req := client.NewDeleteUHadoopInstanceRequest() + cmd := &cobra.Command{ + Use: "delete ", + Short: "Delete a UHadoop cluster", + Long: `Delete a UHadoop cluster by instance ID`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + req.InstanceId = sdkStr(args[0]) + resp, err := client.DeleteUHadoopInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.PrintJSON(resp) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + req.ReleaseEIP = cmd.Flags().Bool("release-eip", false, "Optional. Release bound EIP after deletion") + + command.SetFlagValues(cmd, "release-eip", "true", "false") + + return cmd +} diff --git a/products/uhadoop/internal/uhadoop/describe.go b/products/uhadoop/internal/uhadoop/describe.go new file mode 100644 index 0000000000..ff28f982cf --- /dev/null +++ b/products/uhadoop/internal/uhadoop/describe.go @@ -0,0 +1,86 @@ +package uhadoop + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/ucloud/response" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// describeClusterResponse mirrors the real API response for DescribeUHadoopInstance. +// The SDK's ClusterInfo has CreateTime/ExpireTime as string but the API returns +// Unix timestamps (int), and many fields are missing or wrongly typed. +type describeClusterResponse struct { + response.CommonBase + ClusterSet []describeClusterInfo `json:"ClusterSet"` +} + +type describeClusterInfo struct { + InstanceId string `json:"InstanceId"` + ClusterInstanceId string `json:"ClusterInstanceId"` + InstanceName string `json:"InstanceName"` + ClusterInstanceName string `json:"ClusterInstanceName"` + FlinkResourceId string `json:"FlinkResourceId"` + Framework string `json:"Framework"` + FrameworkVersion string `json:"FrameworkVersion"` + ReleaseVersion string `json:"ReleaseVersion"` + HadoopVersion string `json:"HadoopVersion"` + State string `json:"State"` + Zone string `json:"Zone"` + VPCId string `json:"VPCId"` + SubnetId string `json:"SubnetId"` + BusinessId string `json:"BusinessId"` + ChargeType string `json:"ChargeType"` + Tag string `json:"Tag"` + CreateTime int64 `json:"CreateTime"` + ExpireTime int64 `json:"ExpireTime"` + RunningTime int64 `json:"RunningTime"` + MasterCount int `json:"MasterCount"` + CoreCount int `json:"CoreCount"` + TaskCount int `json:"TaskCount"` + NodeCount int `json:"NodeCount"` + RedundantCount int `json:"RedundantCount"` + AppConfigCount int `json:"AppConfigCount"` + IsOpenSecGroup bool `json:"IsOpenSecGroup"` + HdfsTotal int `json:"HdfsTotal"` + HdfsUsed int `json:"HdfsUsed"` + // Complex nested fields (NodeSet/AppConfigSet) kept as interface{} to avoid + // deep struct definitions; omitted in table mode, full in JSON. + NodeSet []interface{} `json:"NodeSet"` + AppConfigSet []interface{} `json:"AppConfigSet"` +} + +// newDescribe ucloud uhadoop describe +func newDescribe(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + req := client.NewDescribeUHadoopInstanceRequest() + cmd := &cobra.Command{ + Use: "describe ", + Short: "Describe a UHadoop cluster", + Long: `Describe a UHadoop cluster with detailed information`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + req.InstanceId = sdkStr(args[0]) + var resp describeClusterResponse + err := client.InvokeAction("DescribeUHadoopInstance", req, &resp) + if err != nil { + ctx.HandleError(err) + return + } + ctx.PrintList(resp.ClusterSet) + }, + } + cmd.Flags().SortFlags = false + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + command.SetFlagValues(cmd, "region", ctx.RegionList()...) + + return cmd +} + +func sdkStr(s string) *string { return &s } diff --git a/products/uhadoop/internal/uhadoop/list.go b/products/uhadoop/internal/uhadoop/list.go new file mode 100644 index 0000000000..6f561b40b4 --- /dev/null +++ b/products/uhadoop/internal/uhadoop/list.go @@ -0,0 +1,203 @@ +package uhadoop + +import ( + "fmt" + "strings" + "time" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/ucloud/response" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + sdkerror "github.com/ucloud/ucloud-sdk-go/ucloud/error" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// listClusterResponse mirrors the real API response for ListUHadoopInstance. +// The SDK's ListClusterInfo has CreateTime/ExpireTime as string but the API +// returns Unix timestamps (int), and misses fields like AutoRenew/HdfsTotal. +type listClusterResponse struct { + response.CommonBase + TotalCount int `json:"TotalCount"` + ClusterSet []listClusterInfo `json:"ClusterSet"` +} + +type listClusterInfo struct { + Zone string `json:"Zone"` + InstanceId string `json:"InstanceId"` + ClusterInstanceId string `json:"ClusterInstanceId"` + InstanceName string `json:"InstanceName"` + ClusterInstanceName string `json:"ClusterInstanceName"` + FlinkResourceId string `json:"FlinkResourceId"` + Framework string `json:"Framework"` + FrameworkVersion string `json:"FrameworkVersion"` + Remark string `json:"Remark"` + CreateTime int64 `json:"CreateTime"` + ExpireTime int64 `json:"ExpireTime"` + AutoRenew int `json:"AutoRenew"` + ChargeType string `json:"ChargeType"` + MasterCount int `json:"MasterCount"` + CoreCount int `json:"CoreCount"` + TaskCount int `json:"TaskCount"` + UHostCount int `json:"UHostCount"` + RedundantCount int `json:"RedundantCount"` + State string `json:"State"` + ReleaseVersion string `json:"ReleaseVersion"` + HadoopVersion string `json:"HadoopVersion"` + VPCId string `json:"VPCId"` + SubnetId string `json:"SubnetId"` + BusinessId string `json:"BusinessId"` + HdfsTotal int `json:"HdfsTotal"` + HdfsUsed int `json:"HdfsUsed"` +} + +// newList ucloud uhadoop list +func newList(ctx *cli.Context) *cobra.Command { + var allRegion, idOnly bool + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + req := client.NewListUHadoopInstanceRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List all UHadoop clusters", + Long: `List all UHadoop clusters`, + Run: func(cmd *cobra.Command, args []string) { + clusters, err := getAllClusters(ctx, client, req, allRegion) + if err != nil { + ctx.HandleError(err) + return + } + if idOnly { + listClusterID(ctx, clusters) + } else { + listClusters(ctx, clusters, allRegion) + } + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.Zone = cmd.Flags().String("zone", "", "Optional. Assign availability zone") + req.Limit = cmd.Flags().Int("limit", 60, "Optional. Limit default 60") + req.Offset = cmd.Flags().Int("offset", 0, "Optional. Offset default 0") + cmd.Flags().BoolVar(&allRegion, "all-region", false, "Optional. Accept values: true or false. List clusters of all regions when assigned true") + cmd.Flags().BoolVar(&idOnly, "id-only", false, "Optional. Just display resource id of clusters") + + command.SetFlagValues(cmd, "all-region", "true", "false") + command.SetFlagValues(cmd, "id-only", "true", "false") + + return cmd +} + +func getAllClusters(ctx *cli.Context, client *uhadoopsdk.UHadoopClient, req *uhadoopsdk.ListUHadoopInstanceRequest, allRegion bool) ([]listClusterInfo, error) { + if allRegion { + result := make([]listClusterInfo, 0) + regions, err := ctx.AllRegions() + if err != nil { + return nil, err + } + for _, region := range regions { + _req := *req + _req.Region = sdk.String(region) + clusters, err := fetchClustersPageOff(client, &_req) + if e, ok := err.(sdkerror.Error); ok && e.Code() == _RetCodeRegionNoPermission { + continue + } + if err != nil { + return nil, err + } + result = append(result, clusters...) + } + return result, nil + } + + var resp listClusterResponse + // Use InvokeAction directly with our custom response struct because the + // SDK's ListClusterInfo types CreateTime/ExpireTime as string (wrong: int). + err := client.InvokeAction("ListUHadoopInstance", req, &resp) + if err != nil { + return nil, err + } + return resp.ClusterSet, nil +} + +func fetchClustersPageOff(client *uhadoopsdk.UHadoopClient, req *uhadoopsdk.ListUHadoopInstanceRequest) ([]listClusterInfo, error) { + _req := *req + result := make([]listClusterInfo, 0) + for limit, offset := 60, 0; ; offset += limit { + _req.Offset = sdk.Int(offset) + _req.Limit = sdk.Int(limit) + var resp listClusterResponse + err := client.InvokeAction("ListUHadoopInstance", &_req, &resp) + if err != nil { + return nil, err + } + result = append(result, resp.ClusterSet...) + if len(resp.ClusterSet) < limit { + break + } + } + return result, nil +} + +func listClusters(ctx *cli.Context, clusters []listClusterInfo, listAllRegion bool) { + list := make([]listRow, 0, len(clusters)) + for _, c := range clusters { + list = append(list, toListRow(c)) + } + + if ctx.Format() != cli.OutputTable { + ctx.PrintList(list) + return + } + + rows := make([]listRowDefault, 0, len(list)) + for _, r := range list { + rows = append(rows, listRowDefault{ + InstanceId: r.InstanceId, InstanceName: r.InstanceName, + Framework: r.Framework, ReleaseVersion: r.ReleaseVersion, + HadoopVersion: r.HadoopVersion, State: r.State, + Zone: r.Zone, CreateTime: r.CreateTime, ExpireTime: r.ExpireTime, + }) + } + ctx.PrintList(rows) +} + +func toListRow(c listClusterInfo) listRow { + return listRow{ + InstanceId: c.InstanceId, + InstanceName: c.InstanceName, + Framework: c.Framework, + ReleaseVersion: c.ReleaseVersion, + HadoopVersion: c.HadoopVersion, + State: c.State, + Zone: c.Zone, + VPCId: c.VPCId, + SubnetId: c.SubnetId, + ChargeType: c.ChargeType, + CreateTime: formatUnixTime(c.CreateTime), + ExpireTime: formatUnixTime(c.ExpireTime), + } +} + +func formatUnixTime(ts int64) string { + if ts <= 0 { + return "" + } + return time.Unix(ts, 0).Format("2006-01-02") +} + +func listClusterID(ctx *cli.Context, clusters []listClusterInfo) { + ids := make([]string, 0, len(clusters)) + for _, c := range clusters { + ids = append(ids, c.InstanceId) + } + fmt.Fprintln(ctx.Out(), strings.Join(ids, ",")) +} + +// _RetCodeRegionNoPermission is the SDK RetCode when account lacks permission in the current region. +const _RetCodeRegionNoPermission = 230 diff --git a/products/uhadoop/internal/uhadoop/list_framework_app.go b/products/uhadoop/internal/uhadoop/list_framework_app.go new file mode 100644 index 0000000000..204b63fa3c --- /dev/null +++ b/products/uhadoop/internal/uhadoop/list_framework_app.go @@ -0,0 +1,112 @@ +package uhadoop + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/ucloud/response" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// frameworkAppResponse mirrors the real API response for +// ListUHadoopFrameworkAppByUseCase. The SDK's UseCases type has MustHas as +// string and is missing the Apps field, but the API returns both as []string. +// We bypass the SDK-generated response type and unmarshal into our own struct. +type frameworkAppResponse struct { + response.CommonBase + AppConfigSet []frameworkAppConfigVersion `json:"AppConfigSet"` +} + +type frameworkAppConfigVersion struct { + Framework string `json:"Framework"` + FrameworkVersion string `json:"FrameworkVersion"` + HadoopVersion string `json:"HadoopVersion"` + ReleaseVersion string `json:"ReleaseVersion"` + UseCases []frameworkUseCaseRaw `json:"UseCases"` +} + +type frameworkUseCaseRaw struct { + ClusterCase string `json:"ClusterCase"` + Apps []string `json:"Apps"` + MustHas []string `json:"MustHas"` + AppVersion []frameworkAppEntry `json:"AppVersion"` +} + +type frameworkAppEntry struct { + AppName string `json:"AppName"` + AppVersion string `json:"AppVersion"` +} + +// newListFrameworkApp ucloud uhadoop list-framework-app +func newListFrameworkApp(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + req := client.NewListUHadoopFrameworkAppByUseCaseRequest() + cmd := &cobra.Command{ + Use: "list-framework-app", + Short: "List UHadoop framework apps by use case", + Long: `List available UHadoop frameworks and their applications organized by use case`, + Run: func(cmd *cobra.Command, args []string) { + var resp frameworkAppResponse + // Use InvokeAction directly with our custom response struct because + // the SDK's typed response has MustHas as string (wrong: API returns []string). + err := client.InvokeAction("ListUHadoopFrameworkAppByUseCase", req, &resp) + if err != nil { + ctx.HandleError(err) + return + } + listFrameworkApps(ctx, resp.AppConfigSet) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + + return cmd +} + +func listFrameworkApps(ctx *cli.Context, appConfigs []frameworkAppConfigVersion) { + if ctx.Format() != cli.OutputTable { + ctx.PrintList(appConfigs) + return + } + + list := make([]frameworkRow, 0) + for _, ac := range appConfigs { + for _, uc := range ac.UseCases { + var apps []string + var versions []string + for _, av := range uc.AppVersion { + apps = append(apps, av.AppName) + versions = append(versions, av.AppName+"#"+av.AppVersion) + } + list = append(list, frameworkRow{ + Framework: ac.Framework, + FrameworkVersion: ac.FrameworkVersion, + ReleaseVersion: ac.ReleaseVersion, + HadoopVersion: ac.HadoopVersion, + UseCase: uc.ClusterCase, + Apps: strings.Join(apps, ","), + Versions: strings.Join(versions, ","), + MustHas: strings.Join(uc.MustHas, ","), + }) + } + } + ctx.PrintList(list) +} + +type frameworkRow struct { + Framework string + FrameworkVersion string + ReleaseVersion string + HadoopVersion string + UseCase string + Apps string + Versions string + MustHas string +} diff --git a/products/uhadoop/internal/uhadoop/list_node_type.go b/products/uhadoop/internal/uhadoop/list_node_type.go new file mode 100644 index 0000000000..fe0628b055 --- /dev/null +++ b/products/uhadoop/internal/uhadoop/list_node_type.go @@ -0,0 +1,75 @@ +package uhadoop + +import ( + "strings" + + "github.com/spf13/cobra" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newListNodeType ucloud uhadoop list-node-type +func newListNodeType(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + req := client.NewGetUHadoopNodeTypeRequest() + cmd := &cobra.Command{ + Use: "list-node-type", + Short: "List available node types for UHadoop", + Long: `List available node/instance types for UHadoop clusters`, + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.GetUHadoopNodeType(req) + if err != nil { + ctx.HandleError(err) + return + } + listNodeTypes(ctx, resp.InstanceTypeSet) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + req.Framework = cmd.Flags().String("framework", "", "Optional. Filter by framework: Hadoop|HDFS|MR|StarRocks-Shared-Nothing|StarRocks-Shared-Data") + req.FrameworkVersion = cmd.Flags().String("framework-version", "", "Optional. Filter by framework version, e.g. 3.3.4-udh3.2") + req.NodeRole = cmd.Flags().String("node-role", "", "Optional. Filter by node role: master|core|task") + req.NodeType = cmd.Flags().String("node-type", "", "Optional. Filter by node type name") + + command.SetFlagValues(cmd, "node-role", "master", "core", "task", "client") + + return cmd +} + +func listNodeTypes(ctx *cli.Context, types []uhadoopsdk.InstanceType) { + list := make([]instanceTypeRow, 0, len(types)) + for _, t := range types { + row := instanceTypeRow{ + NodeType: t.NodeType, + HostType: t.HostType, + CPU: t.CPU, + Memory: t.Memory, + CPUToMemoryRatio: t.CPUToMemoryRatio, + SuitableRole: strings.Join(t.SuitableRole, ","), + IsUsable: t.IsUsable, + GpuType: t.GpuType, + GpuCount: t.GpuCount, + } + if len(t.DiskSet) > 0 { + // Find the Data disk info + for _, d := range t.DiskSet { + if d.Type == "Data" { + row.DiskType = strings.Join(d.DiskType, ",") + row.DiskMinSize = d.DiskMinSize + row.DiskMaxSize = d.DiskMaxSize + row.DiskMinNum = d.DiskMinNum + row.DiskMaxNum = d.DiskMaxNum + break + } + } + } + list = append(list, row) + } + ctx.PrintList(list) +} diff --git a/products/uhadoop/internal/uhadoop/restart_service.go b/products/uhadoop/internal/uhadoop/restart_service.go new file mode 100644 index 0000000000..36dc1bd8e4 --- /dev/null +++ b/products/uhadoop/internal/uhadoop/restart_service.go @@ -0,0 +1,51 @@ +package uhadoop + +import ( + "github.com/spf13/cobra" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newRestartService ucloud uhadoop restart-service +func newRestartService(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + req := client.NewRestartUHadoopServiceRequest() + var nodeIds []string + var nodeRoles []string + cmd := &cobra.Command{ + Use: "restart-service", + Short: "Restart/start/stop a UHadoop cluster service", + Long: `Restart, start, or stop a service on a UHadoop cluster`, + Run: func(cmd *cobra.Command, args []string) { + req.NodeId = nodeIds + req.NodeRole = nodeRoles + resp, err := client.RestartUHadoopService(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.PrintJSON(resp) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + req.InstanceId = cmd.Flags().String("instance-id", "", "Required. Cluster instance ID") + req.ServiceName = cmd.Flags().String("service-name", "", "Required. Service name (e.g. Hive, Spark, Hdfs, Yarn)") + req.ApplicationVersion = cmd.Flags().String("application-version", "", "Optional. Application version, if set, operates on all services of the app") + req.OnlyStart = cmd.Flags().Bool("only-start", false, "Optional. Only start the service") + req.OnlyStop = cmd.Flags().Bool("only-stop", false, "Optional. Only stop the service") + cmd.Flags().StringSliceVar(&nodeIds, "node-id", nil, "Optional. Node IDs to filter, can be specified multiple times") + cmd.Flags().StringSliceVar(&nodeRoles, "node-role", nil, "Optional. Node roles to filter: master|core|task, can be specified multiple times") + + command.SetFlagValues(cmd, "only-start", "true", "false") + command.SetFlagValues(cmd, "only-stop", "true", "false") + command.SetFlagValues(cmd, "node-role", "master", "core", "task") + + return cmd +} diff --git a/products/uhadoop/internal/uhadoop/rows.go b/products/uhadoop/internal/uhadoop/rows.go new file mode 100644 index 0000000000..31585fc99d --- /dev/null +++ b/products/uhadoop/internal/uhadoop/rows.go @@ -0,0 +1,59 @@ +package uhadoop + +// listRow is the full row for ListUHadoopInstance output. +type listRow struct { + InstanceId string + InstanceName string + Framework string + ReleaseVersion string + HadoopVersion string + State string + Zone string + VPCId string + SubnetId string + ChargeType string + CreateTime string + ExpireTime string +} + +// listRowDefault is the default (non-wide) column set for list. +type listRowDefault struct { + InstanceId string + InstanceName string + Framework string + ReleaseVersion string + HadoopVersion string + State string + Zone string + CreateTime string + ExpireTime string +} + +// instanceTypeRow is the row for GetUHadoopNodeType output. +type instanceTypeRow struct { + NodeType string + HostType string + CPU string + Memory string + CPUToMemoryRatio string + SuitableRole string + IsUsable string + GpuType string + GpuCount int + DiskType string + DiskMinSize string + DiskMaxSize string + DiskMinNum string + DiskMaxNum string +} + +// frameworkAppRow is the row for ListUHadoopFrameworkAppByUseCase output. +type frameworkAppRow struct { + Framework string + FrameworkVersion string + ReleaseVersion string + HadoopVersion string + UseCase string + Apps string + MustHas string +} diff --git a/products/uhadoop/internal/uhadoop/upgrade_disk.go b/products/uhadoop/internal/uhadoop/upgrade_disk.go new file mode 100644 index 0000000000..91c507dc56 --- /dev/null +++ b/products/uhadoop/internal/uhadoop/upgrade_disk.go @@ -0,0 +1,45 @@ +package uhadoop + +import ( + "github.com/spf13/cobra" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUpgradeDisk ucloud uhadoop upgrade-disk +func newUpgradeDisk(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + req := client.NewUpgradeUHadoopNodeDiskRequest() + var nodeNames []string + cmd := &cobra.Command{ + Use: "upgrade-disk", + Short: "Upgrade UHadoop node disk size", + Long: `Upgrade UHadoop node data disk (and optionally boot disk) size`, + Run: func(cmd *cobra.Command, args []string) { + req.NodeNames = nodeNames + resp, err := client.UpgradeUHadoopNodeDisk(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.PrintJSON(resp) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + req.InstanceId = cmd.Flags().String("instance-id", "", "Required. Cluster instance ID") + req.NodeRole = cmd.Flags().String("node-role", "", "Required. Node role: master|core|task") + req.DataDiskSize = cmd.Flags().Int("data-disk-size", 0, "Required. New data disk size in GB") + req.BootDiskSize = cmd.Flags().Int("boot-disk-size", 0, "Optional. New boot disk size in GB") + cmd.Flags().StringSliceVar(&nodeNames, "node-name", nil, "Optional. Node names, required when NodeRole is not master") + + command.SetFlagValues(cmd, "node-role", "master", "core", "task", "client") + + return cmd +} diff --git a/products/uhadoop/internal/uhadoop/upgrade_node.go b/products/uhadoop/internal/uhadoop/upgrade_node.go new file mode 100644 index 0000000000..b9fed2050f --- /dev/null +++ b/products/uhadoop/internal/uhadoop/upgrade_node.go @@ -0,0 +1,44 @@ +package uhadoop + +import ( + "github.com/spf13/cobra" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUpgradeNode ucloud uhadoop upgrade-node +func newUpgradeNode(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) + req := client.NewUpgradeUHadoopNodeRequest() + var nodeNames []string + cmd := &cobra.Command{ + Use: "upgrade-node", + Short: "Upgrade UHadoop node instance type", + Long: `Upgrade UHadoop node to a new instance type`, + Run: func(cmd *cobra.Command, args []string) { + req.NodeNames = nodeNames + resp, err := client.UpgradeUHadoopNode(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.PrintJSON(resp) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + req.InstanceId = cmd.Flags().String("instance-id", "", "Required. Cluster instance ID") + req.NodeRole = cmd.Flags().String("node-role", "", "Required. Node role: master|core|task") + req.NodeType = cmd.Flags().String("node-type", "", "Required. New node type (from list-node-type)") + cmd.Flags().StringSliceVar(&nodeNames, "node-name", nil, "Optional. Node names, required when NodeRole is not master") + + command.SetFlagValues(cmd, "node-role", "master", "core", "task", "client") + + return cmd +} diff --git a/products/uhadoop/product.go b/products/uhadoop/product.go new file mode 100644 index 0000000000..53ff5cf816 --- /dev/null +++ b/products/uhadoop/product.go @@ -0,0 +1,21 @@ +package uhadoop + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internaluhadoop "github.com/ucloud/ucloud-cli/products/uhadoop/internal/uhadoop" +) + +type product struct{} + +// New returns the uhadoop product (registered via hack/gen-products). +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "uhadoop", Commands: []string{"uhadoop"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internaluhadoop.NewCommand(ctx)} +} diff --git a/products/uhadoop/product.yaml b/products/uhadoop/product.yaml new file mode 100644 index 0000000000..27ac828adc --- /dev/null +++ b/products/uhadoop/product.yaml @@ -0,0 +1,6 @@ +name: uhadoop +owners: + - mark.wei +commands: + - uhadoop +enabled: true diff --git a/products/uhadoop/testdata/cmdtree.golden b/products/uhadoop/testdata/cmdtree.golden new file mode 100644 index 0000000000..413e9445d2 --- /dev/null +++ b/products/uhadoop/testdata/cmdtree.golden @@ -0,0 +1,92 @@ +ucloud uhadoop use=uhadoop short=List,create,delete,describe UHadoop clusters and manage nodes and services +ucloud uhadoop add-node use=add-node short=Add nodes to a UHadoop cluster + flag=boot-disk-size short= default= required= + flag=boot-disk-type short= default= required= + flag=data-disk-num short= default= required= + flag=data-disk-size short= default= required= + flag=data-disk-type short= default= required= + flag=instance-id short= default= required= + flag=node-count short= default=1 required= + flag=node-role short= default= required= + flag=node-type short= default= required= + flag=password short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud uhadoop create use=create short=Create a UHadoop cluster + flag=app-config short= default=[] required= + flag=business-id short= default=Default required= + flag=charge-type short= default=Month required= + flag=framework short= default= required= + flag=framework-version short= default= required= + flag=instance-group short= default=[] required= + flag=meta-store short= default= required= + flag=name short= default= required= + flag=password short= default= required= + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=sec-group-ids short= default= required= + flag=security-enabled short= default= required= + flag=storage-cluster-id short= default= required= + flag=subnet-id short= default= required= + flag=us3-access-key short= default= required= + flag=us3-bucket short= default= required= + flag=us3-secret-key short= default= required= + flag=us3-token-name short= default= required= + flag=vpc-id short= default= required= + flag=zone short= default= required= +ucloud uhadoop delete use=delete short=Delete a UHadoop cluster + flag=project-id short= default= required= + flag=region short= default= required= + flag=release-eip short= default=false required= + flag=zone short= default= required= +ucloud uhadoop describe use=describe short=Describe a UHadoop cluster + flag=region short= default= required= +ucloud uhadoop list use=list short=List all UHadoop clusters + flag=all-region short= default=false required= + flag=id-only short= default=false required= + flag=limit short= default=60 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud uhadoop list-framework-app use=list-framework-app short=List UHadoop framework apps by use case + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud uhadoop list-node-type use=list-node-type short=List available node types for UHadoop + flag=framework short= default= required= + flag=framework-version short= default= required= + flag=node-role short= default= required= + flag=node-type short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud uhadoop restart-service use=restart-service short=Restart/start/stop a UHadoop cluster service + flag=application-version short= default= required= + flag=instance-id short= default= required= + flag=node-id short= default=[] required= + flag=node-role short= default=[] required= + flag=only-start short= default=false required= + flag=only-stop short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-name short= default= required= + flag=zone short= default= required= +ucloud uhadoop upgrade-disk use=upgrade-disk short=Upgrade UHadoop node disk size + flag=boot-disk-size short= default=0 required= + flag=data-disk-size short= default=0 required= + flag=instance-id short= default= required= + flag=node-name short= default=[] required= + flag=node-role short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud uhadoop upgrade-node use=upgrade-node short=Upgrade UHadoop node instance type + flag=instance-id short= default= required= + flag=node-name short= default=[] required= + flag=node-role short= default= required= + flag=node-type short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= diff --git a/products/uhadoop/testdata/completion.golden b/products/uhadoop/testdata/completion.golden new file mode 100644 index 0000000000..73f87471ff --- /dev/null +++ b/products/uhadoop/testdata/completion.golden @@ -0,0 +1,38 @@ +ucloud uhadoop add-node node-role static client,core,task +ucloud uhadoop add-node project-id dynamic +ucloud uhadoop add-node region dynamic +ucloud uhadoop add-node zone dynamic +ucloud uhadoop create charge-type static Dynamic,Month,Year +ucloud uhadoop create project-id dynamic +ucloud uhadoop create region dynamic +ucloud uhadoop create security-enabled static false,true +ucloud uhadoop create zone dynamic +ucloud uhadoop delete project-id dynamic +ucloud uhadoop delete region dynamic +ucloud uhadoop delete release-eip static false,true +ucloud uhadoop delete zone dynamic +ucloud uhadoop describe region dynamic +ucloud uhadoop list all-region static false,true +ucloud uhadoop list id-only static false,true +ucloud uhadoop list project-id dynamic +ucloud uhadoop list region dynamic +ucloud uhadoop list-framework-app project-id dynamic +ucloud uhadoop list-framework-app region dynamic +ucloud uhadoop list-framework-app zone dynamic +ucloud uhadoop list-node-type node-role static client,core,master,task +ucloud uhadoop list-node-type region dynamic +ucloud uhadoop list-node-type zone dynamic +ucloud uhadoop restart-service node-role static core,master,task +ucloud uhadoop restart-service only-start static false,true +ucloud uhadoop restart-service only-stop static false,true +ucloud uhadoop restart-service project-id dynamic +ucloud uhadoop restart-service region dynamic +ucloud uhadoop restart-service zone dynamic +ucloud uhadoop upgrade-disk node-role static client,core,master,task +ucloud uhadoop upgrade-disk project-id dynamic +ucloud uhadoop upgrade-disk region dynamic +ucloud uhadoop upgrade-disk zone dynamic +ucloud uhadoop upgrade-node node-role static client,core,master,task +ucloud uhadoop upgrade-node project-id dynamic +ucloud uhadoop upgrade-node region dynamic +ucloud uhadoop upgrade-node zone dynamic From 83c703787befc5ae173e50d2bea8a5c265e20257 Mon Sep 17 00:00:00 2001 From: "mark.wei" Date: Tue, 14 Jul 2026 11:12:09 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- products/uhadoop/TEST.md | 319 +++++++++++------- products/uhadoop/internal/uhadoop/add_node.go | 63 ++-- products/uhadoop/internal/uhadoop/create.go | 152 ++++----- products/uhadoop/internal/uhadoop/delete.go | 33 +- products/uhadoop/internal/uhadoop/describe.go | 96 +++--- products/uhadoop/internal/uhadoop/list.go | 7 +- .../internal/uhadoop/list_framework_app.go | 17 +- .../internal/uhadoop/list_node_type.go | 17 +- .../internal/uhadoop/restart_service.go | 44 ++- .../uhadoop/internal/uhadoop/upgrade_disk.go | 54 ++- .../uhadoop/internal/uhadoop/upgrade_node.go | 57 +++- products/uhadoop/testdata/cmdtree.golden | 72 ++-- products/uhadoop/testdata/completion.golden | 14 +- 13 files changed, 589 insertions(+), 356 deletions(-) diff --git a/products/uhadoop/TEST.md b/products/uhadoop/TEST.md index 6ee6bbf6cd..841982acf1 100644 --- a/products/uhadoop/TEST.md +++ b/products/uhadoop/TEST.md @@ -9,31 +9,27 @@ ucloud config list # 2. 查看可用地域 ucloud region -# 3. 设置 GOROOT 使用正确的 Go 工具链(g use 1.25.12 后需要) -export GOROOT=/Users/user/.g/go GOTOOLCHAIN=local +# 3. 切换 Go 版本并设置别名(一劳永逸) +g use 1.25.12 +alias go=/Users/user/.g/go/bin/go # 4. 记录测试用的 region 和 zone,后续命令中替换 # 示例: --region cn-bj2 --zone cn-bj2-02 ``` -> **编译/运行命令前缀**: 后续所有 `go run/build/test` 命令需加 `GOROOT=/Users/user/.g/go GOTOOLCHAIN=local` - -### 测试用参数 - -| 参数 | 示例值 | 说明 | -|------|--------|------| -| `` | `cn-bj2` | 地域 | -| `` | `cn-bj2-02` | 可用区 | -| `` | `uhadoop-xxxxx` | 已有集群 ID | -| `` | `uvpc-xxxxx` | VPC ID(可选,不传则自动发现) | -| `` | `subnet-xxxxx` | 子网 ID(可选,不传则自动发现) | - --- ### 一、只读命令测试 #### 1.1 list-framework-app — 查询框架和应用 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `--region` | ✅ | 地域 | +| `--zone` | ✅ | 可用区 | +| `--project-id` | | 项目 ID | +| `--output` | | json / table / yaml | + ```bash # help go run . uhadoop list-framework-app --help @@ -43,14 +39,24 @@ go run . uhadoop list-framework-app --region --zone # JSON 输出 go run . uhadoop list-framework-app --region --zone --output json -``` -**验证点**:返回表格含 Framework、FrameworkVersion、ReleaseVersion、UseCase、Apps、Versions、MustHas 列。 +# 缺参立即报错 +go run . uhadoop list-framework-app +# → --region is required +``` --- #### 1.2 list-node-type — 查询可用机型 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `--region` | ✅ | 地域 | +| `--zone` | ✅ | 可用区 | +| `--framework` | | 按框架过滤:Hadoop/HDFS/MR/StarRocks-* | +| `--framework-version` | | 按框架版本过滤 | +| `--node-role` | | 按角色过滤:master/core/task/client | + ```bash # help go run . uhadoop list-node-type --help @@ -58,26 +64,29 @@ go run . uhadoop list-node-type --help # 全部机型 go run . uhadoop list-node-type --region --zone -# 按框架过滤 -go run . uhadoop list-node-type \ - --region --zone \ - --framework Hadoop --framework-version 3.3.4-udh3.2 - -# 按角色过滤 +# 按框架+角色过滤 go run . uhadoop list-node-type \ --region --zone \ + --framework Hadoop --framework-version 3.3.4-udh3.2 \ --node-role master # JSON 输出 go run . uhadoop list-node-type --region --zone --output json ``` -**验证点**:返回表格含 NodeType、HostType、CPU、Memory、SuitableRole、IsUsable、GpuType、GpuCount、DiskMinSize、DiskMaxSize 列。 - --- #### 1.3 list — 列出集群 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `--region` | ✅ | 地域 | +| `--zone` | | 可用区 | +| `--limit` | | 每页数量,默认 60 | +| `--offset` | | 偏移量,默认 0 | +| `--all-region` | | 跨地域查询 | +| `--id-only` | | 只输出 ID 列表 | + ```bash # help go run . uhadoop list --help @@ -85,49 +94,57 @@ go run . uhadoop list --help # 基本查询 go run . uhadoop list --region -# 按 zone 过滤 -go run . uhadoop list --region --zone - -# 分页 -go run . uhadoop list --region --limit 5 --offset 0 +# 按 zone 过滤 + 分页 +go run . uhadoop list --region --zone --limit 5 --offset 0 # 只输出 ID go run . uhadoop list --region --id-only -# JSON 输出 -go run . uhadoop list --region --output json - -# 跨地域 +# JSON 跨地域 go run . uhadoop list --all-region --output json ``` -**验证点**:表格含 InstanceId、InstanceName、Framework、ReleaseVersion、HadoopVersion、State、Zone、CreateTime、ExpireTime 列。 - --- #### 1.4 describe — 查询集群详情 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `` | ✅ | 位置参数,跟在 describe 后面 | +| `--region` | ✅ | 地域 | +| `--zone` | ✅ | 可用区 | + ```bash # help go run . uhadoop describe --help -# 查询(替换 为上一步 list 返回的 ID) +# 查询 go run . uhadoop describe --region --zone # JSON 输出 go run . uhadoop describe --region --zone --output json ``` -**验证点**:返回 ClusterSet 数组中包含 InstanceId、NodeSet、AppConfigSet 等详情。 - --- -### 二、修改类命令测试(谨慎执行) +### 二、修改类命令测试 -> ⚠️ 以下命令会对真实资源产生修改,请在明确后果后执行。 +> ⚠️ 会对真实资源产生修改 #### 2.1 restart-service — 启停服务 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `--instance-id` | ✅ | 集群 ID | +| `--service-name` | ✅ | 服务名:Hive/Spark/Yarn/Hdfs 等 | +| `--region` | ✅ | 地域 | +| `--zone` | ✅ | 可用区 | +| `--application-version` | | 应用版本,传则操作整个应用 | +| `--only-start` | | 只启动 | +| `--only-stop` | | 只停止 | +| `--node-id` | | 指定节点 ID,可多次传 | +| `--node-role` | | 指定节点角色,可多次传 | + ```bash # help go run . uhadoop restart-service --help @@ -138,19 +155,13 @@ go run . uhadoop restart-service \ --instance-id \ --service-name Hive -# 只停止 +# 只停止/只启动 go run . uhadoop restart-service \ --region --zone \ --instance-id \ --service-name Hive --only-stop -# 只启动 -go run . uhadoop restart-service \ - --region --zone \ - --instance-id \ - --service-name Hive --only-start - -# 指定节点重启 +# 指定节点 go run . uhadoop restart-service \ --region --zone \ --instance-id \ @@ -158,25 +169,37 @@ go run . uhadoop restart-service \ --node-role core --node-role task ``` -**验证点**:返回 `{"RetCode": 0, "State": "running"}`。 - --- #### 2.2 add-node — 扩容节点 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `--instance-id` | ✅ | 集群 ID | +| `--node-role` | ✅ | core / task / client | +| `--node-type` | ✅ | 机型,默认 `o.hadoop2m.xlarge` | +| `--region` | ✅ | 地域 | +| `--zone` | ✅ | 可用区 | +| `--node-count` | | 节点数,默认 1 | +| `--password` | | Client 角色必填,明文(自动 base64) | +| `--boot-disk-size` | | 系统盘 GB,默认 50 | +| `--boot-disk-type` | | 系统盘类型,默认 `CLOUD_RSSD` | +| `--data-disk-size` | | 数据盘 GB,默认 200 | +| `--data-disk-num` | | 数据盘数量,默认 1 | +| `--data-disk-type` | | 数据盘类型,默认 `CLOUD_RSSD` | + ```bash # help go run . uhadoop add-node --help -# Core 节点扩容(磁盘参数有默认值,可省略) +# Core 扩容(全默认磁盘参数) go run . uhadoop add-node \ --region --zone \ --instance-id \ --node-role core \ - --node-type o.hadoop2m.xlarge \ - --node-count 1 + --node-type o.hadoop2m.xlarge -# Task 节点扩容 +# Task 扩容 go run . uhadoop add-node \ --region --zone \ --instance-id \ @@ -184,22 +207,32 @@ go run . uhadoop add-node \ --node-type o.hadoop2m.xlarge \ --node-count 1 -# Client 节点扩容(需要密码,明文即可,自动 base64 编码) +# Client 扩容(需要密码) go run . uhadoop add-node \ --region --zone \ --instance-id \ --node-role client \ --node-type o.hadoop2m.medium \ - --node-count 1 \ --password 'YourPass!' -``` -**验证点**:返回 `{"RetCode": 0}`。 +# 缺参立即报错 +go run . uhadoop add-node --region cn-bj2 --zone cn-bj2-02 +# → --instance-id is required +``` --- #### 2.3 upgrade-node — 升级节点机型 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `--instance-id` | ✅ | 集群 ID | +| `--node-role` | ✅ | master / core / task / client | +| `--node-type` | ✅ | 新机型 | +| `--region` | ✅ | 地域 | +| `--zone` | ✅ | 可用区 | +| `--node-name` | | 节点名,非 master 角色必填 | + ```bash # help go run . uhadoop upgrade-node --help @@ -211,7 +244,7 @@ go run . uhadoop upgrade-node \ --node-role master \ --node-type o.hadoop2m.xlarge -# Core/Task 升级(需要 --node-name,从 describe 返回的 NodeSet 中获取) +# Core 升级(需要 --node-name,从 describe 的 NodeSet 获取) go run . uhadoop upgrade-node \ --region --zone \ --instance-id \ @@ -220,12 +253,20 @@ go run . uhadoop upgrade-node \ --node-name -core1 ``` -**验证点**:返回 `{"RetCode": 0}`。 - --- #### 2.4 upgrade-disk — 扩容磁盘 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `--instance-id` | ✅ | 集群 ID | +| `--node-role` | ✅ | master / core / task / client | +| `--data-disk-size` | ✅ | 新数据盘大小 GB(必须 > 0) | +| `--region` | ✅ | 地域 | +| `--zone` | ✅ | 可用区 | +| `--boot-disk-size` | | 新系统盘大小 GB | +| `--node-name` | | 节点名,非 master 角色必填 | + ```bash # help go run . uhadoop upgrade-disk --help @@ -238,7 +279,7 @@ go run . uhadoop upgrade-disk \ --data-disk-size 500 \ --node-name -core1 -# Master 节点扩容(不需要 --node-name) +# Master 扩容(不需要 --node-name) go run . uhadoop upgrade-disk \ --region --zone \ --instance-id \ @@ -247,20 +288,41 @@ go run . uhadoop upgrade-disk \ --boot-disk-size 100 ``` -**验证点**:返回 `{"RetCode": 0}`。 - --- #### 2.5 create — 创建集群 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `--name` | ✅ | 集群名称 | +| `--framework` | ✅ | Hadoop / HDFS / MR / StarRocks-Shared-Nothing / StarRocks-Shared-Data | +| `--framework-version` | ✅ | 版本号,如 `3.3.4-udh3.2` | +| `--password` | ✅ | 登录密码,明文(自动 base64) | +| `--region` | ✅ | 地域 | +| `--zone` | ✅ | 可用区 | +| `--cluster-case` | ① | Spark / Hbase / Core-Hadoop,自动填充 app-config | +| `--app-config` | ① | 手动指定组件,格式 `App#Version`,可多次传 | +| `--master-node-type` | | Master 机型,默认 `o.hadoop2m.xlarge` | +| `--core-node-type` | | Core 机型,默认 `o.hadoop2m.xlarge` | +| `--task-node-type` | | Task 机型(可选),默认 `o.hadoop2m.xlarge` | +| `--master-count` | | Master 数量,默认 2(StarRocks 默认 3)| +| `--core-count` | | Core 数量,默认 3 | +| `--task-count` | | Task 数量,默认 0 | +| `--vpc-id` | | VPC ID,不传自动发现 | +| `--subnet-id` | | Subnet ID,不传自动发现 | +| `--charge-type` | | 付费类型,默认 Month | +| `--storage-cluster-id` | | MR 框架必填 | + +> ① `--cluster-case` 和 `--app-config` 二选一,至少填一个。 + ```bash # help go run . uhadoop create --help -# Hadoop 集群 — 使用 Spark 模板(推荐方式) +# Spark 模板创建(推荐) go run . uhadoop create \ --region --zone \ - --name test-uhadoop-cli \ + --name test-spark \ --framework Hadoop \ --framework-version 3.3.4-udh3.2 \ --password 'YourPass123!' \ @@ -268,10 +330,10 @@ go run . uhadoop create \ --master-node-type o.hadoop2m.xlarge \ --core-node-type o.hadoop2m.xlarge -# Hadoop 集群 — Hbase 场景 +# Hbase 模板创建 go run . uhadoop create \ --region --zone \ - --name test-hbase-cli \ + --name test-hbase \ --framework Hadoop \ --framework-version 3.3.4-udh3.2 \ --password 'YourPass123!' \ @@ -279,10 +341,10 @@ go run . uhadoop create \ --master-node-type o.hadoop2m.xlarge \ --core-node-type o.hadoop2m.xlarge -# Hadoop 集群 — Core Hadoop 场景 +# Core-Hadoop 模板创建 go run . uhadoop create \ --region --zone \ - --name test-core-cli \ + --name test-core \ --framework Hadoop \ --framework-version 3.3.4-udh3.2 \ --password 'YourPass123!' \ @@ -290,10 +352,10 @@ go run . uhadoop create \ --master-node-type o.hadoop2m.xlarge \ --core-node-type o.hadoop2m.xlarge -# Hadoop 集群 — 手动指定 app-config(不使用模板) +# 手动指定 app-config go run . uhadoop create \ --region --zone \ - --name test-manual-cli \ + --name test-manual \ --framework Hadoop \ --framework-version 3.3.4-udh3.2 \ --password 'YourPass123!' \ @@ -303,12 +365,13 @@ go run . uhadoop create \ --app-config Hive#3.1.3 \ --app-config Zookeeper#3.8.4 \ --app-config Hue#4.11.0 \ - --app-config Hdfs#3.3.4 + --app-config Hdfs#3.3.4 \ + --app-config Yarn#3.3.4 -# MR 集群(需要 --storage-cluster-id,含 Task 节点) +# MR 集群(含 Task 节点) go run . uhadoop create \ --region --zone \ - --name test-mr-cli \ + --name test-mr \ --framework MR \ --framework-version 3.3.4-udh3.2 \ --password 'YourPass123!' \ @@ -318,54 +381,47 @@ go run . uhadoop create \ --core-node-type o.hadoop2m.xlarge \ --task-node-type o.hadoop2m.xlarge --task-count 2 -# StarRocks 集群(--cluster-case 不适用,手动指定 app-config) -go run . uhadoop create \ - --region --zone \ - --name test-sr-cli \ - --framework StarRocks-Shared-Data \ - --framework-version 3.3.4-udh3.2 \ - --password 'YourPass123!' \ - --master-node-type o.hadoop2m.xlarge \ - --task-node-type o.hadoop2m.xlarge --task-count 1 \ - --app-config StarRocks#4.1.1 +# 缺参立即报错 +go run . uhadoop create --region cn-bj2 --zone cn-bj2-02 +# → --name is required ``` -**验证点**:返回 `{"RetCode": 0, "InstanceId": "uhadoop-xxxxx"}`。 - --- #### 2.6 delete — 删除集群 +| 参数 | 必填 | 说明 | +|------|:--:|------| +| `` | ✅ | 位置参数 | +| `--region` | ✅ | 地域 | +| `--zone` | ✅ | 可用区 | +| `--release-eip` | | 是否释放绑定的 EIP | + ```bash # help go run . uhadoop delete --help -# 删除集群(不释放 EIP) -go run . uhadoop delete \ - --region --zone +# 删除(保留 EIP) +go run . uhadoop delete --region --zone -# 删除集群并释放 EIP -go run . uhadoop delete \ - --region --zone \ - --release-eip +# 删除并释放 EIP +go run . uhadoop delete --region --zone --release-eip ``` -**验证点**:返回 `{"RetCode": 0}`。 - --- ### 三、自动化校验 ```bash # 编译 -GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go build ./... +go build ./... -# 运行 uhadoop 模块的 golden 测试 -GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go test ./hack/snapshot -v -run 'ProductGoldens/uhadoop' -GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go test ./hack/snapshot -v -run 'ProductCompletionGoldens/uhadoop' +# 运行 golden 测试 +go test ./hack/snapshot -v -run 'ProductGoldens/uhadoop' +go test ./hack/snapshot -v -run 'ProductCompletionGoldens/uhadoop' -# 运行全部 snapshot 测试 -GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go test ./hack/snapshot -v +# 全部 snapshot +go test ./hack/snapshot -v ``` --- @@ -381,7 +437,26 @@ GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go test ./hack/snapshot -v | `2.6.0-cdh5.13.3` | Spark#2.4.3, Hive#2.3.3, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0 | Hbase#1.2.0, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0, Phoenix#4.14.0 | Hive#2.3.3, Hue#3.10.0, Zookeeper#3.4.5, Yarn#2.6.0 | | `2.6.0-cdh5.4.9` | Spark#2.0.1, Hive#1.2.1, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0 | Hbase#1.0.0, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0, Phoenix#4.6.0 | Hive#1.2.1, Hue#3.10.0, Zookeeper#3.4.5, Yarn#2.6.0 | -> Hadoop 框架自动追加 Hdfs 组件(版本号按上表推)。 +> Hadoop 框架自动追加 Hdfs 组件,MR/HDFS 框架不追加。 + +--- + +### 五、必填参数校验汇总 + +| 命令 | 必填 | +|------|------| +| `list-framework-app` | `--region` `--zone` | +| `list-node-type` | `--region` `--zone` | +| `list` | `--region` | +| `describe ` | `` `--region` `--zone` | +| `create` | `--name` `--framework` `--framework-version` `--password` `--region` `--zone` + `--cluster-case` 或 `--app-config` | +| `delete ` | `` `--region` `--zone` | +| `add-node` | `--instance-id` `--node-role` `--node-type` `--region` `--zone` | +| `restart-service` | `--instance-id` `--service-name` `--region` `--zone` | +| `upgrade-node` | `--instance-id` `--node-role` `--node-type` `--region` `--zone` | +| `upgrade-disk` | `--instance-id` `--node-role` `--data-disk-size` `--region` `--zone` | + +> 缺任何必填参数都会立即报错退出,不会发起 API 调用。 --- @@ -390,25 +465,27 @@ GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go test ./hack/snapshot -v | # | 命令 | 状态 | 备注 | |---|------|------|------| | 1 | `list-framework-app --help` | ⬜ | | -| 2 | `list-framework-app ` | ⬜ | | -| 3 | `list-node-type --help` | ⬜ | | -| 4 | `list-node-type ` | ⬜ | | -| 5 | `list --help` | ⬜ | | -| 6 | `list ` | ⬜ | | -| 7 | `describe --help` | ⬜ | | -| 8 | `describe ` | ⬜ | | -| 9 | `restart-service --help` | ⬜ | | -| 10 | `restart-service ` | ⬜ | | +| 2 | `list-framework-app --region --zone ` | ⬜ | | +| 3 | `list-framework-app` (缺参) | ⬜ | 预期报错 | +| 4 | `list-node-type --help` | ⬜ | | +| 5 | `list-node-type --region --zone ` | ⬜ | | +| 6 | `list --help` | ⬜ | | +| 7 | `list --region ` | ⬜ | | +| 8 | `describe --help` | ⬜ | | +| 9 | `describe --region --zone ` | ⬜ | | +| 10 | `restart-service --help` | ⬜ | | | 11 | `add-node --help` | ⬜ | | -| 12 | `add-node ` | ⬜ | | +| 12 | `add-node` (缺参) | ⬜ | 预期报错 | | 13 | `upgrade-node --help` | ⬜ | | | 14 | `upgrade-disk --help` | ⬜ | | | 15 | `create --help` | ⬜ | | -| 16 | `create --cluster-case Spark` | ⬜ | | -| 17 | `create --cluster-case Hbase` | ⬜ | | -| 18 | `create --cluster-case Core-Hadoop` | ⬜ | | -| 19 | `delete --help` | ⬜ | | -| 20 | `go build ./...` | ⬜ | | -| 21 | `go test ./hack/snapshot -v` | ⬜ | | - -> ⬜ = 待测试   ✅ = 通过   ❌ = 失败 +| 16 | `create` (缺参) | ⬜ | 预期报错 | +| 17 | `create --cluster-case Spark` | ⬜ | | +| 18 | `create --cluster-case Hbase` | ⬜ | | +| 19 | `create --cluster-case Core-Hadoop` | ⬜ | | +| 20 | `create --app-config ...` | ⬜ | | +| 21 | `delete --help` | ⬜ | | +| 22 | `go build ./...` | ⬜ | | +| 23 | `go test ./hack/snapshot -v` | ⬜ | | + +> ⬜ = 待测试   ✅ = 通过   ❌ = 失败 diff --git a/products/uhadoop/internal/uhadoop/add_node.go b/products/uhadoop/internal/uhadoop/add_node.go index af755d237f..d41e6c0316 100644 --- a/products/uhadoop/internal/uhadoop/add_node.go +++ b/products/uhadoop/internal/uhadoop/add_node.go @@ -2,6 +2,7 @@ package uhadoop import ( "encoding/base64" + "fmt" "github.com/spf13/cobra" @@ -11,16 +12,20 @@ import ( "github.com/ucloud/ucloud-cli/pkg/command" ) -// newAddNode ucloud uhadoop add-node func newAddNode(ctx *cli.Context) *cobra.Command { - var rawPassword string + var ( + async *bool + rawPassword string + ) client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewAddUHadoopInstanceNodeRequest() cmd := &cobra.Command{ - Use: "add-node", - Short: "Add nodes to a UHadoop cluster", - Long: `Add a number of nodes to an existing UHadoop cluster`, + Use: "add-node", + Short: "Add nodes to a UHadoop cluster", + Long: `Add a number of nodes to an existing UHadoop cluster`, + SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() if rawPassword != "" { req.Password = sdkStr(base64.StdEncoding.EncodeToString([]byte(rawPassword))) } @@ -29,26 +34,38 @@ func newAddNode(ctx *cli.Context) *cobra.Command { ctx.HandleError(err) return } - ctx.PrintJSON(resp) + if resp.RetCode != 0 { + ctx.HandleError(fmt.Errorf("[%d] %s", resp.RetCode, resp.Message)) + return + } + text := fmt.Sprintf("uhadoop[%s] adding %d %s node(s)", *req.InstanceId, *req.NodeCount, *req.NodeRole) + if *async { + fmt.Fprintln(w, text) + } else { + ctx.PollerTo(w, describeClusterForPoll(ctx, client)).Spoll(*req.InstanceId, text, []string{StateRunning}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceId, Action: "add-node", Status: "Scaling"}) }, } - cmd.Flags().SortFlags = false - - ctx.BindRegion(cmd, req) - ctx.BindZone(cmd, req) - ctx.BindProjectID(cmd, req) - req.InstanceId = cmd.Flags().String("instance-id", "", "Required. Cluster instance ID") - req.NodeRole = cmd.Flags().String("node-role", "", "Required. Node role: core|task|client") - req.NodeType = cmd.Flags().String("node-type", "", "Required. Node type, e.g. o.hadoop2m.medium (from list-node-type)") - req.NodeCount = cmd.Flags().Int("node-count", 1, "Optional. Number of nodes to add, default 1") - cmd.Flags().StringVar(&rawPassword, "password", "", "Optional. Login password, plain text (NodeRole=client is required)") - req.BootDiskSize = cmd.Flags().String("boot-disk-size", "50", "Optional. Boot disk size in GB for new-type nodes, default 50") - req.BootDiskType = cmd.Flags().String("boot-disk-type", "CLOUD_RSSD", "Optional. Boot disk type for new-type nodes, default CLOUD_RSSD") - req.DataDiskSize = cmd.Flags().String("data-disk-size", "200", "Optional. Data disk size in GB for new-type nodes, default 200") - req.DataDiskNum = cmd.Flags().String("data-disk-num", "1", "Optional. Data disk number for new-type nodes, default 1") - req.DataDiskType = cmd.Flags().String("data-disk-type", "CLOUD_RSSD", "Optional. Data disk type for new-type nodes, default CLOUD_RSSD") - + flags := cmd.Flags() + flags.SortFlags = false + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", "", "Optional. Assign availability zone") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.InstanceId = flags.String("instance-id", "", "Required. Cluster instance ID") + req.NodeRole = flags.String("node-role", "", "Required. Node role: core|task|client") + req.NodeType = flags.String("node-type", "", "Required. Node type") + req.NodeCount = flags.Int("node-count", 1, "Number of nodes, default 1") + flags.StringVar(&rawPassword, "password", "", "Login password (client role requires)") + req.BootDiskSize = flags.String("boot-disk-size", "50", "Boot disk GB, default 50") + req.BootDiskType = flags.String("boot-disk-type", "CLOUD_RSSD", "Boot disk type, default CLOUD_RSSD") + req.DataDiskSize = flags.String("data-disk-size", "200", "Data disk GB, default 200") + req.DataDiskNum = flags.String("data-disk-num", "1", "Data disk num, default 1") + req.DataDiskType = flags.String("data-disk-type", "CLOUD_RSSD", "Data disk type, default CLOUD_RSSD") + async = flags.Bool("async", false, "Optional. Do not wait for node addition to finish") command.SetFlagValues(cmd, "node-role", "core", "task", "client") - + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("node-role") + cmd.MarkFlagRequired("node-type") return cmd } diff --git a/products/uhadoop/internal/uhadoop/create.go b/products/uhadoop/internal/uhadoop/create.go index 92bcadcf4d..37ab06ebb5 100644 --- a/products/uhadoop/internal/uhadoop/create.go +++ b/products/uhadoop/internal/uhadoop/create.go @@ -15,7 +15,6 @@ import ( "github.com/ucloud/ucloud-cli/pkg/command" ) -// instanceGroupConfig matches the real API's InstanceGroupConfigs struct. type instanceGroupConfig struct { NodeRole string `json:"NodeRole"` NodeType string `json:"NodeType"` @@ -27,7 +26,6 @@ type instanceGroupConfig struct { BootDiskType string `json:"BootDiskType"` } -// createRequest is the real request body for CreateUHadoopInstance. type createRequest struct { request.CommonBase InstanceName string `json:"InstanceName"` @@ -51,16 +49,11 @@ type createRequest struct { InstanceGroupConfigs []instanceGroupConfig `json:"InstanceGroupConfigs"` } -// createResponse is the response for CreateUHadoopInstance. type createResponse struct { response.CommonBase InstanceId string `json:"InstanceId"` - Message string `json:"Message"` } -// clusterCaseApps maps (FrameworkVersion, ClusterCase) to app templates. -// "Hadoop" cluster cases: Spark, Hbase, Core Hadoop. -// For "Hadoop" framework, Hdfs is auto-appended. For "MR", it is not. var clusterCaseApps = map[string]map[string][]string{ "3.3.4-udh3.2": { "Spark": {"Spark#3.5.3", "Hive#3.1.3", "Hue#4.11.0", "Zookeeper#3.8.4", "Mysql#8.0.32", "Yarn#3.3.4"}, @@ -94,7 +87,6 @@ var clusterCaseApps = map[string]map[string][]string{ }, } -// hdfsVersionByFramework maps framework-version to the Hdfs app version for Hadoop framework. var hdfsVersionByFramework = map[string]string{ "3.3.4-udh3.2": "Hdfs#3.3.4", "3.3.4-udh3.1": "Hdfs#3.3.4", @@ -104,9 +96,9 @@ var hdfsVersionByFramework = map[string]string{ "2.6.0-cdh5.4.9": "Hdfs#2.6.0", } -// newCreate ucloud uhadoop create func newCreate(ctx *cli.Context) *cobra.Command { var ( + async *bool rawPassword string clusterCase string master instanceGroupConfig @@ -116,10 +108,13 @@ func newCreate(ctx *cli.Context) *cobra.Command { client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) sdkReq := client.NewCreateUHadoopInstanceRequest() cmd := &cobra.Command{ - Use: "create", - Short: "Create a UHadoop cluster", - Long: `Create a UHadoop cluster with specified configuration`, + Use: "create", + Short: "Create a UHadoop cluster", + Long: `Create a UHadoop cluster with specified configuration`, + SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + if sdkReq.Framework != nil && (*sdkReq.Framework == "StarRocks-Shared-Nothing" || *sdkReq.Framework == "StarRocks-Shared-Data") { if !cmd.Flags().Changed("master-count") { master.Count = 3 @@ -127,21 +122,15 @@ func newCreate(ctx *cli.Context) *cobra.Command { } groups := buildGroups(master, core, task) if len(groups) == 0 { - ctx.HandleError(fmt.Errorf("at least one node group (--master-node-type or --core-node-type) is required")) + ctx.HandleError(fmt.Errorf("at least one node group is required")) return } - // Resolve app configs: --cluster-case overrides --app-config appConfigs := sdkReq.AppConfigs if clusterCase != "" { - if sdkReq.FrameworkVersion == nil || *sdkReq.FrameworkVersion == "" { - ctx.HandleError(fmt.Errorf("--framework-version is required with --cluster-case")) - return - } versionMap, ok := clusterCaseApps[*sdkReq.FrameworkVersion] if !ok { - ctx.HandleError(fmt.Errorf("unsupported framework-version %q for --cluster-case, supported: %v", - *sdkReq.FrameworkVersion, supportedVersions())) + ctx.HandleError(fmt.Errorf("unsupported framework-version %q for --cluster-case", *sdkReq.FrameworkVersion)) return } template, ok := versionMap[clusterCase] @@ -150,8 +139,6 @@ func newCreate(ctx *cli.Context) *cobra.Command { return } appConfigs = append([]string(nil), template...) - - // For Hadoop framework, auto-append Hdfs if sdkReq.Framework != nil && *sdkReq.Framework == "Hadoop" { if hdfsVer, ok := hdfsVersionByFramework[*sdkReq.FrameworkVersion]; ok { appConfigs = append(appConfigs, hdfsVer) @@ -212,80 +199,83 @@ func newCreate(ctx *cli.Context) *cobra.Command { ctx.HandleError(err) return } - ctx.PrintJSON(resp) + if resp.RetCode != 0 { + ctx.HandleError(fmt.Errorf("[%d] %s", resp.RetCode, resp.Message)) + return + } + text := fmt.Sprintf("uhadoop[%s] is creating", resp.InstanceId) + if *async { + fmt.Fprintln(w, text) + } else { + ctx.PollerTo(w, describeClusterForPoll(ctx, client)).Spoll(resp.InstanceId, text, []string{StateRunning}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.InstanceId, Action: "create", Status: "Creating"}) }, } - cmd.Flags().SortFlags = false + flags := cmd.Flags() + flags.SortFlags = false + sdkReq.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + sdkReq.Zone = flags.String("zone", "", "Optional. Assign availability zone") + sdkReq.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + sdkReq.InstanceName = flags.String("name", "", "Required. Instance name") + sdkReq.Framework = flags.String("framework", "", "Required. Framework") + sdkReq.FrameworkVersion = flags.String("framework-version", "", "Required. Framework version") + flags.StringVar(&rawPassword, "password", "", "Required. Login password") + sdkReq.VPCId = flags.String("vpc-id", "", "Optional. VPC ID") + sdkReq.SubnetId = flags.String("subnet-id", "", "Optional. Subnet ID") + sdkReq.ChargeType = flags.String("charge-type", "Month", "Optional. Charge type") + sdkReq.Quantity = flags.Int("quantity", 1, "Optional. Quantity") + sdkReq.BusinessId = flags.String("business-id", "Default", "Optional. Business group") + sdkReq.StorgeClusterId = flags.String("storage-cluster-id", "", "Optional. Storage cluster ID (MR framework)") + sdkReq.StandAloneMetaStore = flags.String("meta-store", "", "Optional. Meta store type") + sdkReq.IsSecurityEnabled = flags.String("security-enabled", "", "Optional. Enable security group") + sdkReq.SecGroupIds = flags.String("sec-group-ids", "", "Optional. Security group IDs") + sdkReq.US3Bucket = flags.String("us3-bucket", "", "Optional. US3 bucket") + sdkReq.US3AccessKey = flags.String("us3-access-key", "", "Optional. US3 access key") + sdkReq.US3SecretKey = flags.String("us3-secret-key", "", "Optional. US3 secret key") + sdkReq.US3TokenName = flags.String("us3-token-name", "", "Optional. US3 token name") + flags.StringVar(&clusterCase, "cluster-case", "", "Cluster use case: Spark|Hbase|Core-Hadoop") - ctx.BindRegion(cmd, sdkReq) - ctx.BindZone(cmd, sdkReq) - ctx.BindProjectID(cmd, sdkReq) - sdkReq.InstanceName = cmd.Flags().String("name", "", "Required. Instance name") - sdkReq.Framework = cmd.Flags().String("framework", "", "Required. Framework: Hadoop|HDFS|MR|StarRocks-Shared-Nothing|StarRocks-Shared-Data") - sdkReq.FrameworkVersion = cmd.Flags().String("framework-version", "", "Required. Framework version, e.g. 3.3.4-udh3.2") - cmd.Flags().StringVar(&rawPassword, "password", "", "Required. Login password (plain text, auto base64 encoded)") - sdkReq.VPCId = cmd.Flags().String("vpc-id", "", "Optional. VPC ID, auto-discovered if not set") - sdkReq.SubnetId = cmd.Flags().String("subnet-id", "", "Optional. Subnet ID, auto-discovered if not set") - sdkReq.ChargeType = cmd.Flags().String("charge-type", "Month", "Optional. Charge type: Month|Year|Dynamic") - sdkReq.Quantity = cmd.Flags().Int("quantity", 1, "Optional. Quantity for charge type") - sdkReq.BusinessId = cmd.Flags().String("business-id", "Default", "Optional. Business group ID") - sdkReq.StorgeClusterId = cmd.Flags().String("storage-cluster-id", "", "Optional. Storage cluster ID for MR framework") - sdkReq.StandAloneMetaStore = cmd.Flags().String("meta-store", "", "Optional. Stand alone meta store type, e.g. 'udb'") - sdkReq.IsSecurityEnabled = cmd.Flags().String("security-enabled", "", "Optional. Enable security group: true|false") - sdkReq.SecGroupIds = cmd.Flags().String("sec-group-ids", "", "Optional. Security group IDs, comma separated") - sdkReq.US3Bucket = cmd.Flags().String("us3-bucket", "", "Optional. US3 bucket name for StarRocks-Shared-Data") - sdkReq.US3AccessKey = cmd.Flags().String("us3-access-key", "", "Optional. US3 access key for StarRocks-Shared-Data") - sdkReq.US3SecretKey = cmd.Flags().String("us3-secret-key", "", "Optional. US3 secret key for StarRocks-Shared-Data") - sdkReq.US3TokenName = cmd.Flags().String("us3-token-name", "", "Optional. US3 token name for StarRocks-Shared-Data") - - // Cluster case flag — when set, auto-fills app-config based on the template - cmd.Flags().StringVar(&clusterCase, "cluster-case", "", "Optional. Cluster use case: Spark|Hbase|Core-Hadoop. When set, --app-config is auto-filled from the template") - - // Master node group master.NodeRole = "master" - cmd.Flags().StringVar(&master.NodeType, "master-node-type", "o.hadoop2m.xlarge", "Master node type, default o.hadoop2m.xlarge") - cmd.Flags().IntVar(&master.Count, "master-count", 2, "Master node count, default 2") - cmd.Flags().IntVar(&master.DataDiskSize, "master-data-disk-size", 100, "Master data disk size in GB, default 100") - cmd.Flags().IntVar(&master.DataDiskNum, "master-data-disk-num", 1, "Master data disk number, default 1") - cmd.Flags().StringVar(&master.DataDiskType, "master-data-disk-type", "CLOUD_RSSD", "Master data disk type, default CLOUD_RSSD") - cmd.Flags().IntVar(&master.BootDiskSize, "master-boot-disk-size", 50, "Master boot disk size in GB, default 50") - cmd.Flags().StringVar(&master.BootDiskType, "master-boot-disk-type", "CLOUD_RSSD", "Master boot disk type, default CLOUD_RSSD") + flags.StringVar(&master.NodeType, "master-node-type", "o.hadoop2m.xlarge", "Master node type") + flags.IntVar(&master.Count, "master-count", 2, "Master node count") + flags.IntVar(&master.DataDiskSize, "master-data-disk-size", 100, "Master data disk GB") + flags.IntVar(&master.DataDiskNum, "master-data-disk-num", 1, "Master data disk num") + flags.StringVar(&master.DataDiskType, "master-data-disk-type", "CLOUD_RSSD", "Master data disk type") + flags.IntVar(&master.BootDiskSize, "master-boot-disk-size", 50, "Master boot disk GB") + flags.StringVar(&master.BootDiskType, "master-boot-disk-type", "CLOUD_RSSD", "Master boot disk type") - // Core node group core.NodeRole = "core" - cmd.Flags().StringVar(&core.NodeType, "core-node-type", "o.hadoop2m.xlarge", "Core node type, default o.hadoop2m.xlarge") - cmd.Flags().IntVar(&core.Count, "core-count", 3, "Core node count, default 3") - cmd.Flags().IntVar(&core.DataDiskSize, "core-data-disk-size", 200, "Core data disk size in GB, default 200") - cmd.Flags().IntVar(&core.DataDiskNum, "core-data-disk-num", 1, "Core data disk number, default 1") - cmd.Flags().StringVar(&core.DataDiskType, "core-data-disk-type", "CLOUD_RSSD", "Core data disk type, default CLOUD_RSSD") - cmd.Flags().IntVar(&core.BootDiskSize, "core-boot-disk-size", 50, "Core boot disk size in GB, default 50") - cmd.Flags().StringVar(&core.BootDiskType, "core-boot-disk-type", "CLOUD_RSSD", "Core boot disk type, default CLOUD_RSSD") + flags.StringVar(&core.NodeType, "core-node-type", "o.hadoop2m.xlarge", "Core node type") + flags.IntVar(&core.Count, "core-count", 3, "Core node count") + flags.IntVar(&core.DataDiskSize, "core-data-disk-size", 200, "Core data disk GB") + flags.IntVar(&core.DataDiskNum, "core-data-disk-num", 1, "Core data disk num") + flags.StringVar(&core.DataDiskType, "core-data-disk-type", "CLOUD_RSSD", "Core data disk type") + flags.IntVar(&core.BootDiskSize, "core-boot-disk-size", 50, "Core boot disk GB") + flags.StringVar(&core.BootDiskType, "core-boot-disk-type", "CLOUD_RSSD", "Core boot disk type") - // Task node group (optional) task.NodeRole = "task" - cmd.Flags().StringVar(&task.NodeType, "task-node-type", "o.hadoop2m.xlarge", "Optional. Task node type, default o.hadoop2m.xlarge") - cmd.Flags().IntVar(&task.Count, "task-count", 0, "Task node count, default 0 (no task nodes)") - cmd.Flags().IntVar(&task.DataDiskSize, "task-data-disk-size", 200, "Task data disk size in GB, default 200") - cmd.Flags().IntVar(&task.DataDiskNum, "task-data-disk-num", 1, "Task data disk number, default 1") - cmd.Flags().StringVar(&task.DataDiskType, "task-data-disk-type", "CLOUD_RSSD", "Task data disk type, default CLOUD_RSSD") - cmd.Flags().IntVar(&task.BootDiskSize, "task-boot-disk-size", 50, "Task boot disk size in GB, default 50") - cmd.Flags().StringVar(&task.BootDiskType, "task-boot-disk-type", "CLOUD_RSSD", "Task boot disk type, default CLOUD_RSSD") + flags.StringVar(&task.NodeType, "task-node-type", "o.hadoop2m.xlarge", "Optional. Task node type") + flags.IntVar(&task.Count, "task-count", 0, "Task node count") + flags.IntVar(&task.DataDiskSize, "task-data-disk-size", 200, "Task data disk GB") + flags.IntVar(&task.DataDiskNum, "task-data-disk-num", 1, "Task data disk num") + flags.StringVar(&task.DataDiskType, "task-data-disk-type", "CLOUD_RSSD", "Task data disk type") + flags.IntVar(&task.BootDiskSize, "task-boot-disk-size", 50, "Task boot disk GB") + flags.StringVar(&task.BootDiskType, "task-boot-disk-type", "CLOUD_RSSD", "Task boot disk type") - cmd.Flags().StringSliceVar(&sdkReq.AppConfigs, "app-config", nil, "App configs in format App#Version. Ignored when --cluster-case is set") + async = flags.Bool("async", false, "Optional. Do not wait for creation to finish") + flags.StringSliceVar(&sdkReq.AppConfigs, "app-config", nil, "App configs: App#Version") command.SetFlagValues(cmd, "cluster-case", "Spark", "Hbase", "Core-Hadoop") command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic") command.SetFlagValues(cmd, "security-enabled", "true", "false") - return cmd -} + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("framework") + cmd.MarkFlagRequired("framework-version") + cmd.MarkFlagRequired("password") -func supportedVersions() []string { - vs := make([]string, 0, len(clusterCaseApps)) - for v := range clusterCaseApps { - vs = append(vs, v) - } - return vs + return cmd } func buildGroups(master, core, task instanceGroupConfig) []instanceGroupConfig { diff --git a/products/uhadoop/internal/uhadoop/delete.go b/products/uhadoop/internal/uhadoop/delete.go index 051ba5659f..21f76aa12a 100644 --- a/products/uhadoop/internal/uhadoop/delete.go +++ b/products/uhadoop/internal/uhadoop/delete.go @@ -1,6 +1,8 @@ package uhadoop import ( + "fmt" + "github.com/spf13/cobra" uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" @@ -11,21 +13,35 @@ import ( // newDelete ucloud uhadoop delete func newDelete(ctx *cli.Context) *cobra.Command { + var yes bool client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewDeleteUHadoopInstanceRequest() cmd := &cobra.Command{ - Use: "delete ", - Short: "Delete a UHadoop cluster", - Long: `Delete a UHadoop cluster by instance ID`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - req.InstanceId = sdkStr(args[0]) + Use: "delete ", + Short: "Delete a UHadoop cluster", + Long: `Delete a UHadoop cluster by instance ID`, + Args: cobra.ExactArgs(1), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + id := args[0] + ok, err := ctx.Confirm(yes, fmt.Sprintf("Are you sure you want to delete cluster %s?", id)) + if err != nil { + return err + } + if !ok { + return nil + } + req.InstanceId = sdkStr(id) resp, err := client.DeleteUHadoopInstance(req) if err != nil { - ctx.HandleError(err) - return + return err + } + if resp.RetCode != 0 { + return fmt.Errorf("[%d] %s", resp.RetCode, resp.Message) } + fmt.Fprintf(ctx.Err(), "Cluster %s deleted\n", id) ctx.PrintJSON(resp) + return nil }, } cmd.Flags().SortFlags = false @@ -33,6 +49,7 @@ func newDelete(ctx *cli.Context) *cobra.Command { ctx.BindRegion(cmd, req) ctx.BindZone(cmd, req) ctx.BindProjectID(cmd, req) + cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation.") req.ReleaseEIP = cmd.Flags().Bool("release-eip", false, "Optional. Release bound EIP after deletion") command.SetFlagValues(cmd, "release-eip", "true", "false") diff --git a/products/uhadoop/internal/uhadoop/describe.go b/products/uhadoop/internal/uhadoop/describe.go index ff28f982cf..e0a7e8e50a 100644 --- a/products/uhadoop/internal/uhadoop/describe.go +++ b/products/uhadoop/internal/uhadoop/describe.go @@ -3,6 +3,7 @@ package uhadoop import ( "github.com/spf13/cobra" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" "github.com/ucloud/ucloud-sdk-go/ucloud/response" uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" @@ -11,58 +12,55 @@ import ( "github.com/ucloud/ucloud-cli/pkg/command" ) -// describeClusterResponse mirrors the real API response for DescribeUHadoopInstance. -// The SDK's ClusterInfo has CreateTime/ExpireTime as string but the API returns -// Unix timestamps (int), and many fields are missing or wrongly typed. +const StateRunning = "Running" + type describeClusterResponse struct { response.CommonBase ClusterSet []describeClusterInfo `json:"ClusterSet"` } type describeClusterInfo struct { - InstanceId string `json:"InstanceId"` - ClusterInstanceId string `json:"ClusterInstanceId"` - InstanceName string `json:"InstanceName"` - ClusterInstanceName string `json:"ClusterInstanceName"` - FlinkResourceId string `json:"FlinkResourceId"` - Framework string `json:"Framework"` - FrameworkVersion string `json:"FrameworkVersion"` - ReleaseVersion string `json:"ReleaseVersion"` - HadoopVersion string `json:"HadoopVersion"` - State string `json:"State"` - Zone string `json:"Zone"` - VPCId string `json:"VPCId"` - SubnetId string `json:"SubnetId"` - BusinessId string `json:"BusinessId"` - ChargeType string `json:"ChargeType"` - Tag string `json:"Tag"` - CreateTime int64 `json:"CreateTime"` - ExpireTime int64 `json:"ExpireTime"` - RunningTime int64 `json:"RunningTime"` - MasterCount int `json:"MasterCount"` - CoreCount int `json:"CoreCount"` - TaskCount int `json:"TaskCount"` - NodeCount int `json:"NodeCount"` - RedundantCount int `json:"RedundantCount"` - AppConfigCount int `json:"AppConfigCount"` - IsOpenSecGroup bool `json:"IsOpenSecGroup"` - HdfsTotal int `json:"HdfsTotal"` - HdfsUsed int `json:"HdfsUsed"` - // Complex nested fields (NodeSet/AppConfigSet) kept as interface{} to avoid - // deep struct definitions; omitted in table mode, full in JSON. - NodeSet []interface{} `json:"NodeSet"` - AppConfigSet []interface{} `json:"AppConfigSet"` + InstanceId string `json:"InstanceId"` + ClusterInstanceId string `json:"ClusterInstanceId"` + InstanceName string `json:"InstanceName"` + ClusterInstanceName string `json:"ClusterInstanceName"` + FlinkResourceId string `json:"FlinkResourceId"` + Framework string `json:"Framework"` + FrameworkVersion string `json:"FrameworkVersion"` + ReleaseVersion string `json:"ReleaseVersion"` + HadoopVersion string `json:"HadoopVersion"` + State string `json:"State"` + Zone string `json:"Zone"` + VPCId string `json:"VPCId"` + SubnetId string `json:"SubnetId"` + BusinessId string `json:"BusinessId"` + ChargeType string `json:"ChargeType"` + Tag string `json:"Tag"` + CreateTime int64 `json:"CreateTime"` + ExpireTime int64 `json:"ExpireTime"` + RunningTime int64 `json:"RunningTime"` + MasterCount int `json:"MasterCount"` + CoreCount int `json:"CoreCount"` + TaskCount int `json:"TaskCount"` + NodeCount int `json:"NodeCount"` + RedundantCount int `json:"RedundantCount"` + AppConfigCount int `json:"AppConfigCount"` + IsOpenSecGroup bool `json:"IsOpenSecGroup"` + HdfsTotal int `json:"HdfsTotal"` + HdfsUsed int `json:"HdfsUsed"` + NodeSet []interface{} `json:"NodeSet"` + AppConfigSet []interface{} `json:"AppConfigSet"` } -// newDescribe ucloud uhadoop describe func newDescribe(ctx *cli.Context) *cobra.Command { client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewDescribeUHadoopInstanceRequest() cmd := &cobra.Command{ - Use: "describe ", - Short: "Describe a UHadoop cluster", - Long: `Describe a UHadoop cluster with detailed information`, - Args: cobra.ExactArgs(1), + Use: "describe ", + Short: "Describe a UHadoop cluster", + Long: `Describe a UHadoop cluster with detailed information`, + SilenceUsage: true, + Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { req.InstanceId = sdkStr(args[0]) var resp describeClusterResponse @@ -77,10 +75,24 @@ func newDescribe(ctx *cli.Context) *cobra.Command { cmd.Flags().SortFlags = false ctx.BindRegion(cmd, req) ctx.BindZone(cmd, req) - command.SetFlagValues(cmd, "region", ctx.RegionList()...) - return cmd } func sdkStr(s string) *string { return &s } + +func describeClusterForPoll(ctx *cli.Context, client *uhadoopsdk.UHadoopClient) func(string, *request.CommonBase) (interface{}, error) { + return func(id string, _ *request.CommonBase) (interface{}, error) { + req := client.NewDescribeUHadoopInstanceRequest() + req.InstanceId = sdkStr(id) + var resp describeClusterResponse + err := client.InvokeAction("DescribeUHadoopInstance", req, &resp) + if err != nil { + return nil, err + } + if len(resp.ClusterSet) == 0 { + return nil, nil + } + return resp.ClusterSet[0], nil + } +} diff --git a/products/uhadoop/internal/uhadoop/list.go b/products/uhadoop/internal/uhadoop/list.go index 6f561b40b4..f2f2db3b20 100644 --- a/products/uhadoop/internal/uhadoop/list.go +++ b/products/uhadoop/internal/uhadoop/list.go @@ -61,9 +61,10 @@ func newList(ctx *cli.Context) *cobra.Command { client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewListUHadoopInstanceRequest() cmd := &cobra.Command{ - Use: "list", - Short: "List all UHadoop clusters", - Long: `List all UHadoop clusters`, + Use: "list", + Short: "List all UHadoop clusters", + Long: `List all UHadoop clusters`, + SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { clusters, err := getAllClusters(ctx, client, req, allRegion) if err != nil { diff --git a/products/uhadoop/internal/uhadoop/list_framework_app.go b/products/uhadoop/internal/uhadoop/list_framework_app.go index 204b63fa3c..8e0fc806fd 100644 --- a/products/uhadoop/internal/uhadoop/list_framework_app.go +++ b/products/uhadoop/internal/uhadoop/list_framework_app.go @@ -1,6 +1,7 @@ package uhadoop import ( + "fmt" "strings" "github.com/spf13/cobra" @@ -46,10 +47,20 @@ func newListFrameworkApp(ctx *cli.Context) *cobra.Command { client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewListUHadoopFrameworkAppByUseCaseRequest() cmd := &cobra.Command{ - Use: "list-framework-app", - Short: "List UHadoop framework apps by use case", - Long: `List available UHadoop frameworks and their applications organized by use case`, + Use: "list-framework-app", + Short: "List UHadoop framework apps by use case", + Long: `List available UHadoop frameworks and their applications organized by use case`, + SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { + if req.Region == nil || *req.Region == "" { + ctx.HandleError(fmt.Errorf("--region is required")) + return + } + if req.Zone == nil || *req.Zone == "" { + ctx.HandleError(fmt.Errorf("--zone is required")) + return + } + var resp frameworkAppResponse // Use InvokeAction directly with our custom response struct because // the SDK's typed response has MustHas as string (wrong: API returns []string). diff --git a/products/uhadoop/internal/uhadoop/list_node_type.go b/products/uhadoop/internal/uhadoop/list_node_type.go index fe0628b055..d8786f8c35 100644 --- a/products/uhadoop/internal/uhadoop/list_node_type.go +++ b/products/uhadoop/internal/uhadoop/list_node_type.go @@ -1,6 +1,7 @@ package uhadoop import ( + "fmt" "strings" "github.com/spf13/cobra" @@ -16,10 +17,20 @@ func newListNodeType(ctx *cli.Context) *cobra.Command { client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewGetUHadoopNodeTypeRequest() cmd := &cobra.Command{ - Use: "list-node-type", - Short: "List available node types for UHadoop", - Long: `List available node/instance types for UHadoop clusters`, + Use: "list-node-type", + Short: "List available node types for UHadoop", + Long: `List available node/instance types for UHadoop clusters`, + SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { + if req.Region == nil || *req.Region == "" { + ctx.HandleError(fmt.Errorf("--region is required")) + return + } + if req.Zone == nil || *req.Zone == "" { + ctx.HandleError(fmt.Errorf("--zone is required")) + return + } + resp, err := client.GetUHadoopNodeType(req) if err != nil { ctx.HandleError(err) diff --git a/products/uhadoop/internal/uhadoop/restart_service.go b/products/uhadoop/internal/uhadoop/restart_service.go index 36dc1bd8e4..aa5dd0e3fc 100644 --- a/products/uhadoop/internal/uhadoop/restart_service.go +++ b/products/uhadoop/internal/uhadoop/restart_service.go @@ -1,6 +1,8 @@ package uhadoop import ( + "fmt" + "github.com/spf13/cobra" uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" @@ -11,23 +13,52 @@ import ( // newRestartService ucloud uhadoop restart-service func newRestartService(ctx *cli.Context) *cobra.Command { + var yes bool client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewRestartUHadoopServiceRequest() var nodeIds []string var nodeRoles []string cmd := &cobra.Command{ - Use: "restart-service", - Short: "Restart/start/stop a UHadoop cluster service", - Long: `Restart, start, or stop a service on a UHadoop cluster`, - Run: func(cmd *cobra.Command, args []string) { + Use: "restart-service", + Short: "Restart/start/stop a UHadoop cluster service", + Long: `Restart, start, or stop a service on a UHadoop cluster`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + if req.InstanceId == nil || *req.InstanceId == "" { + return fmt.Errorf("--instance-id is required") + } + if req.ServiceName == nil || *req.ServiceName == "" { + return fmt.Errorf("--service-name is required") + } + + action := "restart" + if req.OnlyStart != nil && *req.OnlyStart { + action = "start" + } + if req.OnlyStop != nil && *req.OnlyStop { + action = "stop" + } + + ok, err := ctx.Confirm(yes, fmt.Sprintf("Are you sure you want to %s service %s on cluster %s?", action, *req.ServiceName, *req.InstanceId)) + if err != nil { + return err + } + if !ok { + return nil + } + req.NodeId = nodeIds req.NodeRole = nodeRoles resp, err := client.RestartUHadoopService(req) if err != nil { - ctx.HandleError(err) - return + return err + } + if resp.RetCode != 0 { + return fmt.Errorf("[%d] %s", resp.RetCode, resp.Message) } + fmt.Fprintf(ctx.Err(), "Service %s %s on cluster %s, state: %s\n", *req.ServiceName, action, *req.InstanceId, resp.State) ctx.PrintJSON(resp) + return nil }, } cmd.Flags().SortFlags = false @@ -40,6 +71,7 @@ func newRestartService(ctx *cli.Context) *cobra.Command { req.ApplicationVersion = cmd.Flags().String("application-version", "", "Optional. Application version, if set, operates on all services of the app") req.OnlyStart = cmd.Flags().Bool("only-start", false, "Optional. Only start the service") req.OnlyStop = cmd.Flags().Bool("only-stop", false, "Optional. Only stop the service") + cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation.") cmd.Flags().StringSliceVar(&nodeIds, "node-id", nil, "Optional. Node IDs to filter, can be specified multiple times") cmd.Flags().StringSliceVar(&nodeRoles, "node-role", nil, "Optional. Node roles to filter: master|core|task, can be specified multiple times") diff --git a/products/uhadoop/internal/uhadoop/upgrade_disk.go b/products/uhadoop/internal/uhadoop/upgrade_disk.go index 91c507dc56..c6e1ec11b9 100644 --- a/products/uhadoop/internal/uhadoop/upgrade_disk.go +++ b/products/uhadoop/internal/uhadoop/upgrade_disk.go @@ -1,6 +1,8 @@ package uhadoop import ( + "fmt" + "github.com/spf13/cobra" uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" @@ -9,37 +11,55 @@ import ( "github.com/ucloud/ucloud-cli/pkg/command" ) -// newUpgradeDisk ucloud uhadoop upgrade-disk func newUpgradeDisk(ctx *cli.Context) *cobra.Command { + var yes *bool client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewUpgradeUHadoopNodeDiskRequest() var nodeNames []string cmd := &cobra.Command{ - Use: "upgrade-disk", - Short: "Upgrade UHadoop node disk size", - Long: `Upgrade UHadoop node data disk (and optionally boot disk) size`, + Use: "upgrade-disk", + Short: "Upgrade UHadoop node disk size", + Long: `Upgrade UHadoop node data disk (and optionally boot disk) size`, + SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + ok, err := ctx.Confirm(*yes, fmt.Sprintf("Upgrade disk on %s nodes of cluster %s to %d GB?", *req.NodeRole, *req.InstanceId, *req.DataDiskSize)) + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } req.NodeNames = nodeNames resp, err := client.UpgradeUHadoopNodeDisk(req) if err != nil { ctx.HandleError(err) return } - ctx.PrintJSON(resp) + if resp.RetCode != 0 { + ctx.HandleError(fmt.Errorf("[%d] %s", resp.RetCode, resp.Message)) + return + } + text := fmt.Sprintf("uhadoop[%s] upgrading disk on %s nodes", *req.InstanceId, *req.NodeRole) + ctx.PollerTo(w, describeClusterForPoll(ctx, client)).Spoll(*req.InstanceId, text, []string{StateRunning}) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceId, Action: "upgrade-disk", Status: "Upgrading"}) }, } - cmd.Flags().SortFlags = false - - ctx.BindRegion(cmd, req) - ctx.BindZone(cmd, req) - ctx.BindProjectID(cmd, req) - req.InstanceId = cmd.Flags().String("instance-id", "", "Required. Cluster instance ID") - req.NodeRole = cmd.Flags().String("node-role", "", "Required. Node role: master|core|task") - req.DataDiskSize = cmd.Flags().Int("data-disk-size", 0, "Required. New data disk size in GB") - req.BootDiskSize = cmd.Flags().Int("boot-disk-size", 0, "Optional. New boot disk size in GB") - cmd.Flags().StringSliceVar(&nodeNames, "node-name", nil, "Optional. Node names, required when NodeRole is not master") - + flags := cmd.Flags() + flags.SortFlags = false + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", "", "Optional. Assign availability zone") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.InstanceId = flags.String("instance-id", "", "Required. Cluster instance ID") + req.NodeRole = flags.String("node-role", "", "Required. Node role: master|core|task") + req.DataDiskSize = flags.Int("data-disk-size", 0, "Required. New data disk size in GB") + req.BootDiskSize = flags.Int("boot-disk-size", 0, "Optional. New boot disk size in GB") + yes = flags.BoolP("yes", "y", false, "Do not prompt for confirmation") + flags.StringSliceVar(&nodeNames, "node-name", nil, "Node names, required when NodeRole != master") command.SetFlagValues(cmd, "node-role", "master", "core", "task", "client") - + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("node-role") + cmd.MarkFlagRequired("data-disk-size") return cmd } diff --git a/products/uhadoop/internal/uhadoop/upgrade_node.go b/products/uhadoop/internal/uhadoop/upgrade_node.go index b9fed2050f..77cf901d2b 100644 --- a/products/uhadoop/internal/uhadoop/upgrade_node.go +++ b/products/uhadoop/internal/uhadoop/upgrade_node.go @@ -1,6 +1,8 @@ package uhadoop import ( + "fmt" + "github.com/spf13/cobra" uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" @@ -9,36 +11,59 @@ import ( "github.com/ucloud/ucloud-cli/pkg/command" ) -// newUpgradeNode ucloud uhadoop upgrade-node func newUpgradeNode(ctx *cli.Context) *cobra.Command { + var yes, async *bool client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewUpgradeUHadoopNodeRequest() var nodeNames []string cmd := &cobra.Command{ - Use: "upgrade-node", - Short: "Upgrade UHadoop node instance type", - Long: `Upgrade UHadoop node to a new instance type`, + Use: "upgrade-node", + Short: "Upgrade UHadoop node instance type", + Long: `Upgrade UHadoop node to a new instance type`, + SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + ok, err := ctx.Confirm(*yes, fmt.Sprintf("Upgrade %s nodes on cluster %s to %s?", *req.NodeRole, *req.InstanceId, *req.NodeType)) + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } req.NodeNames = nodeNames resp, err := client.UpgradeUHadoopNode(req) if err != nil { ctx.HandleError(err) return } - ctx.PrintJSON(resp) + if resp.RetCode != 0 { + ctx.HandleError(fmt.Errorf("[%d] %s", resp.RetCode, resp.Message)) + return + } + text := fmt.Sprintf("uhadoop[%s] upgrading %s nodes", *req.InstanceId, *req.NodeRole) + if *async { + fmt.Fprintln(w, text) + } else { + ctx.PollerTo(w, describeClusterForPoll(ctx, client)).Spoll(*req.InstanceId, text, []string{StateRunning}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceId, Action: "upgrade-node", Status: "Upgrading"}) }, } - cmd.Flags().SortFlags = false - - ctx.BindRegion(cmd, req) - ctx.BindZone(cmd, req) - ctx.BindProjectID(cmd, req) - req.InstanceId = cmd.Flags().String("instance-id", "", "Required. Cluster instance ID") - req.NodeRole = cmd.Flags().String("node-role", "", "Required. Node role: master|core|task") - req.NodeType = cmd.Flags().String("node-type", "", "Required. New node type (from list-node-type)") - cmd.Flags().StringSliceVar(&nodeNames, "node-name", nil, "Optional. Node names, required when NodeRole is not master") - + flags := cmd.Flags() + flags.SortFlags = false + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", "", "Optional. Assign availability zone") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.InstanceId = flags.String("instance-id", "", "Required. Cluster instance ID") + req.NodeRole = flags.String("node-role", "", "Required. Node role: master|core|task") + req.NodeType = flags.String("node-type", "", "Required. New node type") + yes = flags.BoolP("yes", "y", false, "Do not prompt for confirmation") + async = flags.Bool("async", false, "Optional. Do not wait for upgrade to complete") + flags.StringSliceVar(&nodeNames, "node-name", nil, "Node names, required when NodeRole != master") command.SetFlagValues(cmd, "node-role", "master", "core", "task", "client") - + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("node-role") + cmd.MarkFlagRequired("node-type") return cmd } diff --git a/products/uhadoop/testdata/cmdtree.golden b/products/uhadoop/testdata/cmdtree.golden index 413e9445d2..2d095354f7 100644 --- a/products/uhadoop/testdata/cmdtree.golden +++ b/products/uhadoop/testdata/cmdtree.golden @@ -1,28 +1,44 @@ ucloud uhadoop use=uhadoop short=List,create,delete,describe UHadoop clusters and manage nodes and services ucloud uhadoop add-node use=add-node short=Add nodes to a UHadoop cluster - flag=boot-disk-size short= default= required= - flag=boot-disk-type short= default= required= - flag=data-disk-num short= default= required= - flag=data-disk-size short= default= required= - flag=data-disk-type short= default= required= - flag=instance-id short= default= required= + flag=async short= default=false required= + flag=boot-disk-size short= default=50 required= + flag=boot-disk-type short= default=CLOUD_RSSD required= + flag=data-disk-num short= default=1 required= + flag=data-disk-size short= default=200 required= + flag=data-disk-type short= default=CLOUD_RSSD required= + flag=instance-id short= default= required=true flag=node-count short= default=1 required= - flag=node-role short= default= required= - flag=node-type short= default= required= + flag=node-role short= default= required=true + flag=node-type short= default= required=true flag=password short= default= required= flag=project-id short= default= required= flag=region short= default= required= flag=zone short= default= required= ucloud uhadoop create use=create short=Create a UHadoop cluster flag=app-config short= default=[] required= + flag=async short= default=false required= flag=business-id short= default=Default required= flag=charge-type short= default=Month required= - flag=framework short= default= required= - flag=framework-version short= default= required= - flag=instance-group short= default=[] required= + flag=cluster-case short= default= required= + flag=core-boot-disk-size short= default=50 required= + flag=core-boot-disk-type short= default=CLOUD_RSSD required= + flag=core-count short= default=3 required= + flag=core-data-disk-num short= default=1 required= + flag=core-data-disk-size short= default=200 required= + flag=core-data-disk-type short= default=CLOUD_RSSD required= + flag=core-node-type short= default=o.hadoop2m.xlarge required= + flag=framework short= default= required=true + flag=framework-version short= default= required=true + flag=master-boot-disk-size short= default=50 required= + flag=master-boot-disk-type short= default=CLOUD_RSSD required= + flag=master-count short= default=2 required= + flag=master-data-disk-num short= default=1 required= + flag=master-data-disk-size short= default=100 required= + flag=master-data-disk-type short= default=CLOUD_RSSD required= + flag=master-node-type short= default=o.hadoop2m.xlarge required= flag=meta-store short= default= required= - flag=name short= default= required= - flag=password short= default= required= + flag=name short= default= required=true + flag=password short= default= required=true flag=project-id short= default= required= flag=quantity short= default=1 required= flag=region short= default= required= @@ -30,19 +46,28 @@ ucloud uhadoop create use=create short=Create a UHadoop cluster flag=security-enabled short= default= required= flag=storage-cluster-id short= default= required= flag=subnet-id short= default= required= + flag=task-boot-disk-size short= default=50 required= + flag=task-boot-disk-type short= default=CLOUD_RSSD required= + flag=task-count short= default=0 required= + flag=task-data-disk-num short= default=1 required= + flag=task-data-disk-size short= default=200 required= + flag=task-data-disk-type short= default=CLOUD_RSSD required= + flag=task-node-type short= default=o.hadoop2m.xlarge required= flag=us3-access-key short= default= required= flag=us3-bucket short= default= required= flag=us3-secret-key short= default= required= flag=us3-token-name short= default= required= flag=vpc-id short= default= required= flag=zone short= default= required= -ucloud uhadoop delete use=delete short=Delete a UHadoop cluster +ucloud uhadoop delete use=delete short=Delete a UHadoop cluster flag=project-id short= default= required= flag=region short= default= required= flag=release-eip short= default=false required= + flag=yes short=y default=false required= flag=zone short= default= required= -ucloud uhadoop describe use=describe short=Describe a UHadoop cluster +ucloud uhadoop describe use=describe short=Describe a UHadoop cluster flag=region short= default= required= + flag=zone short= default= required= ucloud uhadoop list use=list short=List all UHadoop clusters flag=all-region short= default=false required= flag=id-only short= default=false required= @@ -72,21 +97,26 @@ ucloud uhadoop restart-service use=restart-service short=Restart/start/stop a UH flag=project-id short= default= required= flag=region short= default= required= flag=service-name short= default= required= + flag=yes short=y default=false required= flag=zone short= default= required= ucloud uhadoop upgrade-disk use=upgrade-disk short=Upgrade UHadoop node disk size + flag=async short= default=false required= flag=boot-disk-size short= default=0 required= - flag=data-disk-size short= default=0 required= - flag=instance-id short= default= required= + flag=data-disk-size short= default=0 required=true + flag=instance-id short= default= required=true flag=node-name short= default=[] required= - flag=node-role short= default= required= + flag=node-role short= default= required=true flag=project-id short= default= required= flag=region short= default= required= + flag=yes short=y default=false required= flag=zone short= default= required= ucloud uhadoop upgrade-node use=upgrade-node short=Upgrade UHadoop node instance type - flag=instance-id short= default= required= + flag=async short= default=false required= + flag=instance-id short= default= required=true flag=node-name short= default=[] required= - flag=node-role short= default= required= - flag=node-type short= default= required= + flag=node-role short= default= required=true + flag=node-type short= default= required=true flag=project-id short= default= required= flag=region short= default= required= + flag=yes short=y default=false required= flag=zone short= default= required= diff --git a/products/uhadoop/testdata/completion.golden b/products/uhadoop/testdata/completion.golden index 73f87471ff..a4603c16e1 100644 --- a/products/uhadoop/testdata/completion.golden +++ b/products/uhadoop/testdata/completion.golden @@ -1,17 +1,13 @@ ucloud uhadoop add-node node-role static client,core,task -ucloud uhadoop add-node project-id dynamic -ucloud uhadoop add-node region dynamic -ucloud uhadoop add-node zone dynamic ucloud uhadoop create charge-type static Dynamic,Month,Year -ucloud uhadoop create project-id dynamic -ucloud uhadoop create region dynamic +ucloud uhadoop create cluster-case static Core-Hadoop,Hbase,Spark ucloud uhadoop create security-enabled static false,true -ucloud uhadoop create zone dynamic ucloud uhadoop delete project-id dynamic ucloud uhadoop delete region dynamic ucloud uhadoop delete release-eip static false,true ucloud uhadoop delete zone dynamic ucloud uhadoop describe region dynamic +ucloud uhadoop describe zone dynamic ucloud uhadoop list all-region static false,true ucloud uhadoop list id-only static false,true ucloud uhadoop list project-id dynamic @@ -29,10 +25,4 @@ ucloud uhadoop restart-service project-id dynamic ucloud uhadoop restart-service region dynamic ucloud uhadoop restart-service zone dynamic ucloud uhadoop upgrade-disk node-role static client,core,master,task -ucloud uhadoop upgrade-disk project-id dynamic -ucloud uhadoop upgrade-disk region dynamic -ucloud uhadoop upgrade-disk zone dynamic ucloud uhadoop upgrade-node node-role static client,core,master,task -ucloud uhadoop upgrade-node project-id dynamic -ucloud uhadoop upgrade-node region dynamic -ucloud uhadoop upgrade-node zone dynamic From 6ec810e795f705a5ac304e41aeb6b939a832f1a1 Mon Sep 17 00:00:00 2001 From: "mark.wei" Date: Tue, 14 Jul 2026 11:35:21 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- products/uhadoop/internal/uhadoop/add_node.go | 2 + products/uhadoop/internal/uhadoop/create.go | 2 + products/uhadoop/internal/uhadoop/delete.go | 2 + products/uhadoop/internal/uhadoop/describe.go | 2 + products/uhadoop/internal/uhadoop/list.go | 52 ++++++++++--------- .../internal/uhadoop/list_framework_app.go | 2 + .../internal/uhadoop/list_node_type.go | 2 + .../internal/uhadoop/restart_service.go | 2 + products/uhadoop/internal/uhadoop/rows.go | 26 +++++----- .../uhadoop/internal/uhadoop/upgrade_disk.go | 2 + .../uhadoop/internal/uhadoop/upgrade_node.go | 2 + 11 files changed, 58 insertions(+), 38 deletions(-) diff --git a/products/uhadoop/internal/uhadoop/add_node.go b/products/uhadoop/internal/uhadoop/add_node.go index d41e6c0316..a54a8efbc0 100644 --- a/products/uhadoop/internal/uhadoop/add_node.go +++ b/products/uhadoop/internal/uhadoop/add_node.go @@ -67,5 +67,7 @@ func newAddNode(ctx *cli.Context) *cobra.Command { cmd.MarkFlagRequired("instance-id") cmd.MarkFlagRequired("node-role") cmd.MarkFlagRequired("node-type") + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } diff --git a/products/uhadoop/internal/uhadoop/create.go b/products/uhadoop/internal/uhadoop/create.go index 37ab06ebb5..af7bab88c3 100644 --- a/products/uhadoop/internal/uhadoop/create.go +++ b/products/uhadoop/internal/uhadoop/create.go @@ -275,6 +275,8 @@ func newCreate(ctx *cli.Context) *cobra.Command { cmd.MarkFlagRequired("framework-version") cmd.MarkFlagRequired("password") + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } diff --git a/products/uhadoop/internal/uhadoop/delete.go b/products/uhadoop/internal/uhadoop/delete.go index 21f76aa12a..74b00bffd7 100644 --- a/products/uhadoop/internal/uhadoop/delete.go +++ b/products/uhadoop/internal/uhadoop/delete.go @@ -54,5 +54,7 @@ func newDelete(ctx *cli.Context) *cobra.Command { command.SetFlagValues(cmd, "release-eip", "true", "false") + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } diff --git a/products/uhadoop/internal/uhadoop/describe.go b/products/uhadoop/internal/uhadoop/describe.go index e0a7e8e50a..028b90c3fc 100644 --- a/products/uhadoop/internal/uhadoop/describe.go +++ b/products/uhadoop/internal/uhadoop/describe.go @@ -76,6 +76,8 @@ func newDescribe(ctx *cli.Context) *cobra.Command { ctx.BindRegion(cmd, req) ctx.BindZone(cmd, req) command.SetFlagValues(cmd, "region", ctx.RegionList()...) + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } diff --git a/products/uhadoop/internal/uhadoop/list.go b/products/uhadoop/internal/uhadoop/list.go index f2f2db3b20..9592cf4d45 100644 --- a/products/uhadoop/internal/uhadoop/list.go +++ b/products/uhadoop/internal/uhadoop/list.go @@ -27,32 +27,32 @@ type listClusterResponse struct { } type listClusterInfo struct { - Zone string `json:"Zone"` - InstanceId string `json:"InstanceId"` - ClusterInstanceId string `json:"ClusterInstanceId"` - InstanceName string `json:"InstanceName"` + Zone string `json:"Zone"` + InstanceId string `json:"InstanceId"` + ClusterInstanceId string `json:"ClusterInstanceId"` + InstanceName string `json:"InstanceName"` ClusterInstanceName string `json:"ClusterInstanceName"` - FlinkResourceId string `json:"FlinkResourceId"` - Framework string `json:"Framework"` - FrameworkVersion string `json:"FrameworkVersion"` - Remark string `json:"Remark"` - CreateTime int64 `json:"CreateTime"` - ExpireTime int64 `json:"ExpireTime"` - AutoRenew int `json:"AutoRenew"` - ChargeType string `json:"ChargeType"` - MasterCount int `json:"MasterCount"` - CoreCount int `json:"CoreCount"` - TaskCount int `json:"TaskCount"` - UHostCount int `json:"UHostCount"` - RedundantCount int `json:"RedundantCount"` - State string `json:"State"` - ReleaseVersion string `json:"ReleaseVersion"` - HadoopVersion string `json:"HadoopVersion"` - VPCId string `json:"VPCId"` - SubnetId string `json:"SubnetId"` - BusinessId string `json:"BusinessId"` - HdfsTotal int `json:"HdfsTotal"` - HdfsUsed int `json:"HdfsUsed"` + FlinkResourceId string `json:"FlinkResourceId"` + Framework string `json:"Framework"` + FrameworkVersion string `json:"FrameworkVersion"` + Remark string `json:"Remark"` + CreateTime int64 `json:"CreateTime"` + ExpireTime int64 `json:"ExpireTime"` + AutoRenew int `json:"AutoRenew"` + ChargeType string `json:"ChargeType"` + MasterCount int `json:"MasterCount"` + CoreCount int `json:"CoreCount"` + TaskCount int `json:"TaskCount"` + UHostCount int `json:"UHostCount"` + RedundantCount int `json:"RedundantCount"` + State string `json:"State"` + ReleaseVersion string `json:"ReleaseVersion"` + HadoopVersion string `json:"HadoopVersion"` + VPCId string `json:"VPCId"` + SubnetId string `json:"SubnetId"` + BusinessId string `json:"BusinessId"` + HdfsTotal int `json:"HdfsTotal"` + HdfsUsed int `json:"HdfsUsed"` } // newList ucloud uhadoop list @@ -91,6 +91,8 @@ func newList(ctx *cli.Context) *cobra.Command { command.SetFlagValues(cmd, "all-region", "true", "false") command.SetFlagValues(cmd, "id-only", "true", "false") + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } diff --git a/products/uhadoop/internal/uhadoop/list_framework_app.go b/products/uhadoop/internal/uhadoop/list_framework_app.go index 8e0fc806fd..b7b42e9768 100644 --- a/products/uhadoop/internal/uhadoop/list_framework_app.go +++ b/products/uhadoop/internal/uhadoop/list_framework_app.go @@ -78,6 +78,8 @@ func newListFrameworkApp(ctx *cli.Context) *cobra.Command { ctx.BindZone(cmd, req) ctx.BindProjectID(cmd, req) + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } diff --git a/products/uhadoop/internal/uhadoop/list_node_type.go b/products/uhadoop/internal/uhadoop/list_node_type.go index d8786f8c35..198a0f29f2 100644 --- a/products/uhadoop/internal/uhadoop/list_node_type.go +++ b/products/uhadoop/internal/uhadoop/list_node_type.go @@ -50,6 +50,8 @@ func newListNodeType(ctx *cli.Context) *cobra.Command { command.SetFlagValues(cmd, "node-role", "master", "core", "task", "client") + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } diff --git a/products/uhadoop/internal/uhadoop/restart_service.go b/products/uhadoop/internal/uhadoop/restart_service.go index aa5dd0e3fc..ee29477e82 100644 --- a/products/uhadoop/internal/uhadoop/restart_service.go +++ b/products/uhadoop/internal/uhadoop/restart_service.go @@ -79,5 +79,7 @@ func newRestartService(ctx *cli.Context) *cobra.Command { command.SetFlagValues(cmd, "only-stop", "true", "false") command.SetFlagValues(cmd, "node-role", "master", "core", "task") + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } diff --git a/products/uhadoop/internal/uhadoop/rows.go b/products/uhadoop/internal/uhadoop/rows.go index 31585fc99d..c41171779e 100644 --- a/products/uhadoop/internal/uhadoop/rows.go +++ b/products/uhadoop/internal/uhadoop/rows.go @@ -31,20 +31,20 @@ type listRowDefault struct { // instanceTypeRow is the row for GetUHadoopNodeType output. type instanceTypeRow struct { - NodeType string - HostType string - CPU string - Memory string + NodeType string + HostType string + CPU string + Memory string CPUToMemoryRatio string - SuitableRole string - IsUsable string - GpuType string - GpuCount int - DiskType string - DiskMinSize string - DiskMaxSize string - DiskMinNum string - DiskMaxNum string + SuitableRole string + IsUsable string + GpuType string + GpuCount int + DiskType string + DiskMinSize string + DiskMaxSize string + DiskMinNum string + DiskMaxNum string } // frameworkAppRow is the row for ListUHadoopFrameworkAppByUseCase output. diff --git a/products/uhadoop/internal/uhadoop/upgrade_disk.go b/products/uhadoop/internal/uhadoop/upgrade_disk.go index c6e1ec11b9..48e994d27d 100644 --- a/products/uhadoop/internal/uhadoop/upgrade_disk.go +++ b/products/uhadoop/internal/uhadoop/upgrade_disk.go @@ -61,5 +61,7 @@ func newUpgradeDisk(ctx *cli.Context) *cobra.Command { cmd.MarkFlagRequired("instance-id") cmd.MarkFlagRequired("node-role") cmd.MarkFlagRequired("data-disk-size") + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } diff --git a/products/uhadoop/internal/uhadoop/upgrade_node.go b/products/uhadoop/internal/uhadoop/upgrade_node.go index 77cf901d2b..96479ae38b 100644 --- a/products/uhadoop/internal/uhadoop/upgrade_node.go +++ b/products/uhadoop/internal/uhadoop/upgrade_node.go @@ -65,5 +65,7 @@ func newUpgradeNode(ctx *cli.Context) *cobra.Command { cmd.MarkFlagRequired("instance-id") cmd.MarkFlagRequired("node-role") cmd.MarkFlagRequired("node-type") + cmd.MarkFlagRequired("region") + cmd.MarkFlagRequired("zone") return cmd } From 2e3bfcd2e9ae0f4ed9ffc628829a1614345983e0 Mon Sep 17 00:00:00 2001 From: "mark.wei" Date: Tue, 14 Jul 2026 11:56:17 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9owner=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- products/uhadoop/product.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/products/uhadoop/product.yaml b/products/uhadoop/product.yaml index 27ac828adc..1d51942310 100644 --- a/products/uhadoop/product.yaml +++ b/products/uhadoop/product.yaml @@ -1,6 +1,6 @@ name: uhadoop owners: - - mark.wei + - mark.wei@ucloud.cn commands: - uhadoop enabled: true From aaf473b2bd01d6a18aa3e5358e6532d48d4d954c Mon Sep 17 00:00:00 2001 From: "mark.wei" Date: Tue, 14 Jul 2026 15:24:47 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- products/uhadoop/TEST.md | 471 ++++++----------------- products/uhadoop/testdata/cmdtree.golden | 41 +- 2 files changed, 129 insertions(+), 383 deletions(-) diff --git a/products/uhadoop/TEST.md b/products/uhadoop/TEST.md index 841982acf1..6d2b9fd0f2 100644 --- a/products/uhadoop/TEST.md +++ b/products/uhadoop/TEST.md @@ -9,483 +9,230 @@ ucloud config list # 2. 查看可用地域 ucloud region -# 3. 切换 Go 版本并设置别名(一劳永逸) +# 3. 切换 Go 版本 g use 1.25.12 alias go=/Users/user/.g/go/bin/go -# 4. 记录测试用的 region 和 zone,后续命令中替换 -# 示例: --region cn-bj2 --zone cn-bj2-02 +# 4. 替换测试参数 +# --region cn-bj2 --zone cn-bj2-02 ``` --- -### 一、只读命令测试 +### 一、查询命令 #### 1.1 list-framework-app — 查询框架和应用 -| 参数 | 必填 | 说明 | -|------|:--:|------| -| `--region` | ✅ | 地域 | -| `--zone` | ✅ | 可用区 | -| `--project-id` | | 项目 ID | -| `--output` | | json / table / yaml | - ```bash -# help -go run . uhadoop list-framework-app --help - -# table 输出 go run . uhadoop list-framework-app --region --zone - -# JSON 输出 go run . uhadoop list-framework-app --region --zone --output json - -# 缺参立即报错 -go run . uhadoop list-framework-app -# → --region is required ``` ---- - #### 1.2 list-node-type — 查询可用机型 -| 参数 | 必填 | 说明 | -|------|:--:|------| -| `--region` | ✅ | 地域 | -| `--zone` | ✅ | 可用区 | -| `--framework` | | 按框架过滤:Hadoop/HDFS/MR/StarRocks-* | -| `--framework-version` | | 按框架版本过滤 | -| `--node-role` | | 按角色过滤:master/core/task/client | - ```bash -# help -go run . uhadoop list-node-type --help - -# 全部机型 go run . uhadoop list-node-type --region --zone - -# 按框架+角色过滤 -go run . uhadoop list-node-type \ - --region --zone \ - --framework Hadoop --framework-version 3.3.4-udh3.2 \ - --node-role master - -# JSON 输出 -go run . uhadoop list-node-type --region --zone --output json +go run . uhadoop list-node-type --region --zone --node-role master +go run . uhadoop list-node-type --region --zone --framework Hadoop --framework-version 3.3.4-udh3.2 ``` ---- - #### 1.3 list — 列出集群 -| 参数 | 必填 | 说明 | -|------|:--:|------| -| `--region` | ✅ | 地域 | -| `--zone` | | 可用区 | -| `--limit` | | 每页数量,默认 60 | -| `--offset` | | 偏移量,默认 0 | -| `--all-region` | | 跨地域查询 | -| `--id-only` | | 只输出 ID 列表 | - ```bash -# help -go run . uhadoop list --help - -# 基本查询 go run . uhadoop list --region - -# 按 zone 过滤 + 分页 -go run . uhadoop list --region --zone --limit 5 --offset 0 - -# 只输出 ID +go run . uhadoop list --region --zone --limit 5 go run . uhadoop list --region --id-only - -# JSON 跨地域 go run . uhadoop list --all-region --output json ``` ---- - #### 1.4 describe — 查询集群详情 -| 参数 | 必填 | 说明 | -|------|:--:|------| -| `` | ✅ | 位置参数,跟在 describe 后面 | -| `--region` | ✅ | 地域 | -| `--zone` | ✅ | 可用区 | - ```bash -# help -go run . uhadoop describe --help - -# 查询 go run . uhadoop describe --region --zone - -# JSON 输出 go run . uhadoop describe --region --zone --output json ``` --- -### 二、修改类命令测试 +### 二、修改命令 > ⚠️ 会对真实资源产生修改 -#### 2.1 restart-service — 启停服务 +#### 2.1 create — 创建集群 | 参数 | 必填 | 说明 | |------|:--:|------| -| `--instance-id` | ✅ | 集群 ID | -| `--service-name` | ✅ | 服务名:Hive/Spark/Yarn/Hdfs 等 | | `--region` | ✅ | 地域 | | `--zone` | ✅ | 可用区 | -| `--application-version` | | 应用版本,传则操作整个应用 | -| `--only-start` | | 只启动 | -| `--only-stop` | | 只停止 | -| `--node-id` | | 指定节点 ID,可多次传 | -| `--node-role` | | 指定节点角色,可多次传 | +| `--name` | ✅ | 集群名称 | +| `--framework` | ✅ | Hadoop / HDFS / MR / StarRocks-* | +| `--framework-version` | ✅ | 如 `3.3.4-udh3.2` | +| `--password` | ✅ | 登录密码(明文,自动 base64) | +| `--master-node-type` | | Master 机型,默认 `o.hadoop2m.xlarge` | +| `--core-node-type` | | Core 机型,默认 `o.hadoop2m.xlarge` | +| `--task-node-type` | | Task 机型(可选) | +| `--master-count` | | 默认 2(StarRocks 默认 3) | +| `--core-count` | | 默认 3 | +| `--task-count` | | 默认 0 | +| `--cluster-case` | | Spark / Hbase / Core-Hadoop | +| `--app-config` | | 手动指定组件,格式 `App#Version` | +| `--vpc-id` | | 可选 | +| `--subnet-id` | | 可选 | +| `--async` | | 异步模式,不等待创建完成 | ```bash -# help -go run . uhadoop restart-service --help - -# 重启服务 -go run . uhadoop restart-service \ +# Spark 模板创建 +go run . uhadoop create \ --region --zone \ - --instance-id \ - --service-name Hive + --name test-spark --framework Hadoop \ + --framework-version 3.3.4-udh3.2 \ + --password 'YourPass123!' \ + --cluster-case Spark -# 只停止/只启动 -go run . uhadoop restart-service \ +# 手动指定 app-config +go run . uhadoop create \ --region --zone \ - --instance-id \ - --service-name Hive --only-stop + --name test-manual --framework Hadoop \ + --framework-version 3.3.4-udh3.2 \ + --password 'YourPass123!' \ + --app-config Spark#3.5.3 --app-config Hive#3.1.3 -# 指定节点 -go run . uhadoop restart-service \ +# MR 集群(需要 --storage-cluster-id) +go run . uhadoop create \ --region --zone \ - --instance-id \ - --service-name Hive \ - --node-role core --node-role task + --name test-mr --framework MR \ + --framework-version 3.3.4-udh3.2 \ + --password 'YourPass123!' \ + --storage-cluster-id \ + --cluster-case Spark ``` ---- - -#### 2.2 add-node — 扩容节点 +#### 2.2 delete — 删除集群 | 参数 | 必填 | 说明 | |------|:--:|------| -| `--instance-id` | ✅ | 集群 ID | -| `--node-role` | ✅ | core / task / client | -| `--node-type` | ✅ | 机型,默认 `o.hadoop2m.xlarge` | +| `` | ✅ | 位置参数 | | `--region` | ✅ | 地域 | | `--zone` | ✅ | 可用区 | -| `--node-count` | | 节点数,默认 1 | -| `--password` | | Client 角色必填,明文(自动 base64) | -| `--boot-disk-size` | | 系统盘 GB,默认 50 | -| `--boot-disk-type` | | 系统盘类型,默认 `CLOUD_RSSD` | -| `--data-disk-size` | | 数据盘 GB,默认 200 | -| `--data-disk-num` | | 数据盘数量,默认 1 | -| `--data-disk-type` | | 数据盘类型,默认 `CLOUD_RSSD` | +| `--release-eip` | | 释放 EIP | +| `--yes` / `-y` | | 跳过确认 | ```bash -# help -go run . uhadoop add-node --help - -# Core 扩容(全默认磁盘参数) -go run . uhadoop add-node \ - --region --zone \ - --instance-id \ - --node-role core \ - --node-type o.hadoop2m.xlarge - -# Task 扩容 -go run . uhadoop add-node \ - --region --zone \ - --instance-id \ - --node-role task \ - --node-type o.hadoop2m.xlarge \ - --node-count 1 - -# Client 扩容(需要密码) -go run . uhadoop add-node \ - --region --zone \ - --instance-id \ - --node-role client \ - --node-type o.hadoop2m.medium \ - --password 'YourPass!' - -# 缺参立即报错 -go run . uhadoop add-node --region cn-bj2 --zone cn-bj2-02 -# → --instance-id is required +go run . uhadoop delete --region --zone +go run . uhadoop delete --region --zone --release-eip -y ``` ---- - -#### 2.3 upgrade-node — 升级节点机型 +#### 2.3 add-node — 扩容节点 | 参数 | 必填 | 说明 | |------|:--:|------| -| `--instance-id` | ✅ | 集群 ID | -| `--node-role` | ✅ | master / core / task / client | -| `--node-type` | ✅ | 新机型 | | `--region` | ✅ | 地域 | | `--zone` | ✅ | 可用区 | -| `--node-name` | | 节点名,非 master 角色必填 | +| `--instance-id` | ✅ | 集群 ID | +| `--node-role` | ✅ | core / task / client | +| `--node-type` | ✅ | 机型 | +| `--node-count` | | 默认 1 | +| `--async` | | 异步模式 | ```bash -# help -go run . uhadoop upgrade-node --help - -# Master 升级(不需要 --node-name) -go run . uhadoop upgrade-node \ +go run . uhadoop add-node \ --region --zone \ - --instance-id \ - --node-role master \ + --instance-id --node-role core \ --node-type o.hadoop2m.xlarge - -# Core 升级(需要 --node-name,从 describe 的 NodeSet 获取) -go run . uhadoop upgrade-node \ - --region --zone \ - --instance-id \ - --node-role core \ - --node-type o.hadoop2m.2xlarge \ - --node-name -core1 ``` ---- - -#### 2.4 upgrade-disk — 扩容磁盘 +#### 2.4 restart-service — 启停服务 | 参数 | 必填 | 说明 | |------|:--:|------| -| `--instance-id` | ✅ | 集群 ID | -| `--node-role` | ✅ | master / core / task / client | -| `--data-disk-size` | ✅ | 新数据盘大小 GB(必须 > 0) | | `--region` | ✅ | 地域 | | `--zone` | ✅ | 可用区 | -| `--boot-disk-size` | | 新系统盘大小 GB | -| `--node-name` | | 节点名,非 master 角色必填 | +| `--instance-id` | ✅ | 集群 ID | +| `--service-name` | ✅ | 服务名 | +| `--only-start` / `--only-stop` | | 只启/只停 | +| `--yes` / `-y` | | 跳过确认 | ```bash -# help -go run . uhadoop upgrade-disk --help - -# Core 节点扩容数据盘到 500G -go run . uhadoop upgrade-disk \ - --region --zone \ - --instance-id \ - --node-role core \ - --data-disk-size 500 \ - --node-name -core1 - -# Master 扩容(不需要 --node-name) -go run . uhadoop upgrade-disk \ +go run . uhadoop restart-service \ --region --zone \ - --instance-id \ - --node-role master \ - --data-disk-size 500 \ - --boot-disk-size 100 + --instance-id --service-name Hive ``` ---- - -#### 2.5 create — 创建集群 +#### 2.5 upgrade-node — 升级节点 | 参数 | 必填 | 说明 | |------|:--:|------| -| `--name` | ✅ | 集群名称 | -| `--framework` | ✅ | Hadoop / HDFS / MR / StarRocks-Shared-Nothing / StarRocks-Shared-Data | -| `--framework-version` | ✅ | 版本号,如 `3.3.4-udh3.2` | -| `--password` | ✅ | 登录密码,明文(自动 base64) | | `--region` | ✅ | 地域 | | `--zone` | ✅ | 可用区 | -| `--cluster-case` | ① | Spark / Hbase / Core-Hadoop,自动填充 app-config | -| `--app-config` | ① | 手动指定组件,格式 `App#Version`,可多次传 | -| `--master-node-type` | | Master 机型,默认 `o.hadoop2m.xlarge` | -| `--core-node-type` | | Core 机型,默认 `o.hadoop2m.xlarge` | -| `--task-node-type` | | Task 机型(可选),默认 `o.hadoop2m.xlarge` | -| `--master-count` | | Master 数量,默认 2(StarRocks 默认 3)| -| `--core-count` | | Core 数量,默认 3 | -| `--task-count` | | Task 数量,默认 0 | -| `--vpc-id` | | VPC ID,不传自动发现 | -| `--subnet-id` | | Subnet ID,不传自动发现 | -| `--charge-type` | | 付费类型,默认 Month | -| `--storage-cluster-id` | | MR 框架必填 | - -> ① `--cluster-case` 和 `--app-config` 二选一,至少填一个。 +| `--instance-id` | ✅ | 集群 ID | +| `--node-role` | ✅ | master / core / task | +| `--node-type` | ✅ | 新机型 | +| `--node-name` | | 节点名(非 master 必填) | +| `--async` | | 异步模式 | +| `--yes` / `-y` | | 跳过确认 | ```bash -# help -go run . uhadoop create --help - -# Spark 模板创建(推荐) -go run . uhadoop create \ - --region --zone \ - --name test-spark \ - --framework Hadoop \ - --framework-version 3.3.4-udh3.2 \ - --password 'YourPass123!' \ - --cluster-case Spark \ - --master-node-type o.hadoop2m.xlarge \ - --core-node-type o.hadoop2m.xlarge - -# Hbase 模板创建 -go run . uhadoop create \ - --region --zone \ - --name test-hbase \ - --framework Hadoop \ - --framework-version 3.3.4-udh3.2 \ - --password 'YourPass123!' \ - --cluster-case Hbase \ - --master-node-type o.hadoop2m.xlarge \ - --core-node-type o.hadoop2m.xlarge - -# Core-Hadoop 模板创建 -go run . uhadoop create \ - --region --zone \ - --name test-core \ - --framework Hadoop \ - --framework-version 3.3.4-udh3.2 \ - --password 'YourPass123!' \ - --cluster-case Core-Hadoop \ - --master-node-type o.hadoop2m.xlarge \ - --core-node-type o.hadoop2m.xlarge - -# 手动指定 app-config -go run . uhadoop create \ - --region --zone \ - --name test-manual \ - --framework Hadoop \ - --framework-version 3.3.4-udh3.2 \ - --password 'YourPass123!' \ - --master-node-type o.hadoop2m.xlarge \ - --core-node-type o.hadoop2m.xlarge \ - --app-config Spark#3.5.3 \ - --app-config Hive#3.1.3 \ - --app-config Zookeeper#3.8.4 \ - --app-config Hue#4.11.0 \ - --app-config Hdfs#3.3.4 \ - --app-config Yarn#3.3.4 - -# MR 集群(含 Task 节点) -go run . uhadoop create \ +go run . uhadoop upgrade-node \ --region --zone \ - --name test-mr \ - --framework MR \ - --framework-version 3.3.4-udh3.2 \ - --password 'YourPass123!' \ - --storage-cluster-id \ - --cluster-case Spark \ - --master-node-type o.hadoop2m.xlarge \ - --core-node-type o.hadoop2m.xlarge \ - --task-node-type o.hadoop2m.xlarge --task-count 2 - -# 缺参立即报错 -go run . uhadoop create --region cn-bj2 --zone cn-bj2-02 -# → --name is required + --instance-id --node-role master \ + --node-type o.hadoop2m.xlarge ``` ---- - -#### 2.6 delete — 删除集群 +#### 2.6 upgrade-disk — 扩容磁盘 | 参数 | 必填 | 说明 | |------|:--:|------| -| `` | ✅ | 位置参数 | | `--region` | ✅ | 地域 | | `--zone` | ✅ | 可用区 | -| `--release-eip` | | 是否释放绑定的 EIP | +| `--instance-id` | ✅ | 集群 ID | +| `--node-role` | ✅ | master / core / task | +| `--data-disk-size` | ✅ | 新数据盘大小 GB | +| `--boot-disk-size` | | 新系统盘大小 GB | +| `--node-name` | | 节点名(非 master 必填) | +| `--yes` / `-y` | | 跳过确认 | ```bash -# help -go run . uhadoop delete --help - -# 删除(保留 EIP) -go run . uhadoop delete --region --zone - -# 删除并释放 EIP -go run . uhadoop delete --region --zone --release-eip +go run . uhadoop upgrade-disk \ + --region --zone \ + --instance-id --node-role core \ + --data-disk-size 500 --node-name -core1 ``` --- -### 三、自动化校验 +### 三、校验 ```bash # 编译 go build ./... -# 运行 golden 测试 -go test ./hack/snapshot -v -run 'ProductGoldens/uhadoop' -go test ./hack/snapshot -v -run 'ProductCompletionGoldens/uhadoop' +# golden 测试 +GOROOT=/Users/user/.g/go GOTOOLCHAIN=local go test ./hack/snapshot -v -run 'uhadoop' -# 全部 snapshot -go test ./hack/snapshot -v +# 缺参立即报错 +go run . uhadoop create --name test +# → required flag(s) "framework", "framework-version", "password", "region", "zone" not set ``` --- -### 四、--cluster-case 模板参考 - -| framework-version | Spark | Hbase | Core-Hadoop | -| --- | --- | --- | --- | -| `3.3.4-udh3.2` | Spark#3.5.3, Hive#3.1.3, Hue#4.11.0, Zookeeper#3.8.4, Mysql#8.0.32, Yarn#3.3.4 | Hbase#2.4.18, Hue#4.11.0, Zookeeper#3.8.4, Mysql#8.0.32, Yarn#3.3.4, Phoenix#5.2.1 | Hive#3.1.3, Hue#4.11.0, Zookeeper#3.8.4, Yarn#3.3.4 | -| `3.3.4-udh3.1` | Spark#3.5.3, Hive#3.1.3, Hue#4.11.0, Zookeeper#3.8.4, Mysql#8.0.32, Yarn#3.3.4 | Hbase#2.4.18, Hue#4.11.0, Zookeeper#3.8.4, Mysql#8.0.32, Yarn#3.3.4, Phoenix#5.2.1 | Hive#3.1.3, Hue#4.11.0, Zookeeper#3.8.4, Yarn#3.3.4 | -| `3.2.1-udh3.0` | Spark#3.3.0, Hive#3.1.3, Hue#4.7.1, Zookeeper#3.6.3, Mysql#5.6.47, Yarn#3.2.1 | Hbase#2.2.4, Hue#4.7.1, Zookeeper#3.6.3, Mysql#5.6.47, Yarn#3.2.1, Phoenix#5.1.2 | Hive#3.1.3, Hue#4.7.1, Zookeeper#3.6.3, Yarn#3.2.1 | -| `2.8.5-udh2.2` | Spark#2.4.6, Hive#2.3.6, Hue#4.7.1, Zookeeper#3.4.13, Mysql#5.6.47, Yarn#2.8.5 | Hbase#1.4.10, Hue#4.7.1, Zookeeper#3.4.13, Mysql#5.6.47, Yarn#2.8.5, Phoenix#4.14.3 | Hive#2.3.6, Hue#4.7.1, Zookeeper#3.4.13, Yarn#2.8.5 | -| `2.6.0-cdh5.13.3` | Spark#2.4.3, Hive#2.3.3, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0 | Hbase#1.2.0, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0, Phoenix#4.14.0 | Hive#2.3.3, Hue#3.10.0, Zookeeper#3.4.5, Yarn#2.6.0 | -| `2.6.0-cdh5.4.9` | Spark#2.0.1, Hive#1.2.1, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0 | Hbase#1.0.0, Hue#3.10.0, Zookeeper#3.4.5, Mysql#5.1.73, Yarn#2.6.0, Phoenix#4.6.0 | Hive#1.2.1, Hue#3.10.0, Zookeeper#3.4.5, Yarn#2.6.0 | - -> Hadoop 框架自动追加 Hdfs 组件,MR/HDFS 框架不追加。 - ---- - -### 五、必填参数校验汇总 - -| 命令 | 必填 | -|------|------| -| `list-framework-app` | `--region` `--zone` | -| `list-node-type` | `--region` `--zone` | -| `list` | `--region` | -| `describe ` | `` `--region` `--zone` | -| `create` | `--name` `--framework` `--framework-version` `--password` `--region` `--zone` + `--cluster-case` 或 `--app-config` | -| `delete ` | `` `--region` `--zone` | -| `add-node` | `--instance-id` `--node-role` `--node-type` `--region` `--zone` | -| `restart-service` | `--instance-id` `--service-name` `--region` `--zone` | -| `upgrade-node` | `--instance-id` `--node-role` `--node-type` `--region` `--zone` | -| `upgrade-disk` | `--instance-id` `--node-role` `--data-disk-size` `--region` `--zone` | - -> 缺任何必填参数都会立即报错退出,不会发起 API 调用。 - ---- - -### 测试结果记录 - -| # | 命令 | 状态 | 备注 | -|---|------|------|------| -| 1 | `list-framework-app --help` | ⬜ | | -| 2 | `list-framework-app --region --zone ` | ⬜ | | -| 3 | `list-framework-app` (缺参) | ⬜ | 预期报错 | -| 4 | `list-node-type --help` | ⬜ | | -| 5 | `list-node-type --region --zone ` | ⬜ | | -| 6 | `list --help` | ⬜ | | -| 7 | `list --region ` | ⬜ | | -| 8 | `describe --help` | ⬜ | | -| 9 | `describe --region --zone ` | ⬜ | | -| 10 | `restart-service --help` | ⬜ | | -| 11 | `add-node --help` | ⬜ | | -| 12 | `add-node` (缺参) | ⬜ | 预期报错 | -| 13 | `upgrade-node --help` | ⬜ | | -| 14 | `upgrade-disk --help` | ⬜ | | -| 15 | `create --help` | ⬜ | | -| 16 | `create` (缺参) | ⬜ | 预期报错 | -| 17 | `create --cluster-case Spark` | ⬜ | | -| 18 | `create --cluster-case Hbase` | ⬜ | | -| 19 | `create --cluster-case Core-Hadoop` | ⬜ | | -| 20 | `create --app-config ...` | ⬜ | | -| 21 | `delete --help` | ⬜ | | -| 22 | `go build ./...` | ⬜ | | -| 23 | `go test ./hack/snapshot -v` | ⬜ | | +### 测试结果 + +| # | 命令 | 状态 | +|---|------|------| +| 1 | `list-framework-app` | ⬜ | +| 2 | `list-node-type` | ⬜ | +| 3 | `list` | ⬜ | +| 4 | `describe` | ⬜ | +| 5 | `create --cluster-case Spark` | ⬜ | +| 6 | `create --app-config ...` | ⬜ | +| 7 | `delete` | ⬜ | +| 8 | `add-node` | ⬜ | +| 9 | `restart-service` | ⬜ | +| 10 | `upgrade-node` | ⬜ | +| 11 | `upgrade-disk` | ⬜ | +| 12 | `create` 缺参 | ⬜ | +| 13 | `go build ./...` | ⬜ | +| 14 | `go test ./hack/snapshot -v` | ⬜ | > ⬜ = 待测试   ✅ = 通过   ❌ = 失败 diff --git a/products/uhadoop/testdata/cmdtree.golden b/products/uhadoop/testdata/cmdtree.golden index 2d095354f7..e22acff82a 100644 --- a/products/uhadoop/testdata/cmdtree.golden +++ b/products/uhadoop/testdata/cmdtree.golden @@ -12,8 +12,8 @@ ucloud uhadoop add-node use=add-node short=Add nodes to a UHadoop cluster flag=node-type short= default= required=true flag=password short= default= required= flag=project-id short= default= required= - flag=region short= default= required= - flag=zone short= default= required= + flag=region short= default= required=true + flag=zone short= default= required=true ucloud uhadoop create use=create short=Create a UHadoop cluster flag=app-config short= default=[] required= flag=async short= default=false required= @@ -41,7 +41,7 @@ ucloud uhadoop create use=create short=Create a UHadoop cluster flag=password short= default= required=true flag=project-id short= default= required= flag=quantity short= default=1 required= - flag=region short= default= required= + flag=region short= default= required=true flag=sec-group-ids short= default= required= flag=security-enabled short= default= required= flag=storage-cluster-id short= default= required= @@ -58,35 +58,35 @@ ucloud uhadoop create use=create short=Create a UHadoop cluster flag=us3-secret-key short= default= required= flag=us3-token-name short= default= required= flag=vpc-id short= default= required= - flag=zone short= default= required= + flag=zone short= default= required=true ucloud uhadoop delete use=delete short=Delete a UHadoop cluster flag=project-id short= default= required= - flag=region short= default= required= + flag=region short= default= required=true flag=release-eip short= default=false required= flag=yes short=y default=false required= - flag=zone short= default= required= + flag=zone short= default= required=true ucloud uhadoop describe use=describe short=Describe a UHadoop cluster - flag=region short= default= required= - flag=zone short= default= required= + flag=region short= default= required=true + flag=zone short= default= required=true ucloud uhadoop list use=list short=List all UHadoop clusters flag=all-region short= default=false required= flag=id-only short= default=false required= flag=limit short= default=60 required= flag=offset short= default=0 required= flag=project-id short= default= required= - flag=region short= default= required= - flag=zone short= default= required= + flag=region short= default= required=true + flag=zone short= default= required=true ucloud uhadoop list-framework-app use=list-framework-app short=List UHadoop framework apps by use case flag=project-id short= default= required= - flag=region short= default= required= - flag=zone short= default= required= + flag=region short= default= required=true + flag=zone short= default= required=true ucloud uhadoop list-node-type use=list-node-type short=List available node types for UHadoop flag=framework short= default= required= flag=framework-version short= default= required= flag=node-role short= default= required= flag=node-type short= default= required= - flag=region short= default= required= - flag=zone short= default= required= + flag=region short= default= required=true + flag=zone short= default= required=true ucloud uhadoop restart-service use=restart-service short=Restart/start/stop a UHadoop cluster service flag=application-version short= default= required= flag=instance-id short= default= required= @@ -95,21 +95,20 @@ ucloud uhadoop restart-service use=restart-service short=Restart/start/stop a UH flag=only-start short= default=false required= flag=only-stop short= default=false required= flag=project-id short= default= required= - flag=region short= default= required= + flag=region short= default= required=true flag=service-name short= default= required= flag=yes short=y default=false required= - flag=zone short= default= required= + flag=zone short= default= required=true ucloud uhadoop upgrade-disk use=upgrade-disk short=Upgrade UHadoop node disk size - flag=async short= default=false required= flag=boot-disk-size short= default=0 required= flag=data-disk-size short= default=0 required=true flag=instance-id short= default= required=true flag=node-name short= default=[] required= flag=node-role short= default= required=true flag=project-id short= default= required= - flag=region short= default= required= + flag=region short= default= required=true flag=yes short=y default=false required= - flag=zone short= default= required= + flag=zone short= default= required=true ucloud uhadoop upgrade-node use=upgrade-node short=Upgrade UHadoop node instance type flag=async short= default=false required= flag=instance-id short= default= required=true @@ -117,6 +116,6 @@ ucloud uhadoop upgrade-node use=upgrade-node short=Upgrade UHadoop node instance flag=node-role short= default= required=true flag=node-type short= default= required=true flag=project-id short= default= required= - flag=region short= default= required= + flag=region short= default= required=true flag=yes short=y default=false required= - flag=zone short= default= required= + flag=zone short= default= required=true From 8ad1a25358e78b7c081ef893a2aa4db6eb585a0e Mon Sep 17 00:00:00 2001 From: "mark.wei" Date: Tue, 14 Jul 2026 17:25:21 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- products/uhadoop/internal/uhadoop/add_node.go | 3 ++- products/uhadoop/internal/uhadoop/create.go | 5 +++-- products/uhadoop/internal/uhadoop/upgrade_disk.go | 9 +-------- products/uhadoop/internal/uhadoop/upgrade_node.go | 3 ++- products/uhadoop/product.yaml | 2 +- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/products/uhadoop/internal/uhadoop/add_node.go b/products/uhadoop/internal/uhadoop/add_node.go index a54a8efbc0..63b5e6cefc 100644 --- a/products/uhadoop/internal/uhadoop/add_node.go +++ b/products/uhadoop/internal/uhadoop/add_node.go @@ -3,6 +3,7 @@ package uhadoop import ( "encoding/base64" "fmt" + "time" "github.com/spf13/cobra" @@ -42,7 +43,7 @@ func newAddNode(ctx *cli.Context) *cobra.Command { if *async { fmt.Fprintln(w, text) } else { - ctx.PollerTo(w, describeClusterForPoll(ctx, client)).Spoll(*req.InstanceId, text, []string{StateRunning}) + ctx.PollerTo(w, describeClusterForPoll(ctx, client), cli.WithTimeout(60*time.Minute)).Spoll(*req.InstanceId, text, []string{StateRunning}) } ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceId, Action: "add-node", Status: "Scaling"}) }, diff --git a/products/uhadoop/internal/uhadoop/create.go b/products/uhadoop/internal/uhadoop/create.go index af7bab88c3..81e5d93d7b 100644 --- a/products/uhadoop/internal/uhadoop/create.go +++ b/products/uhadoop/internal/uhadoop/create.go @@ -3,6 +3,7 @@ package uhadoop import ( "encoding/base64" "fmt" + "time" "github.com/spf13/cobra" @@ -207,7 +208,7 @@ func newCreate(ctx *cli.Context) *cobra.Command { if *async { fmt.Fprintln(w, text) } else { - ctx.PollerTo(w, describeClusterForPoll(ctx, client)).Spoll(resp.InstanceId, text, []string{StateRunning}) + ctx.PollerTo(w, describeClusterForPoll(ctx, client), cli.WithTimeout(60*time.Minute)).Spoll(resp.InstanceId, text, []string{StateRunning}) } ctx.EmitResult(cli.OpResultRow{ResourceID: resp.InstanceId, Action: "create", Status: "Creating"}) }, @@ -237,7 +238,7 @@ func newCreate(ctx *cli.Context) *cobra.Command { flags.StringVar(&clusterCase, "cluster-case", "", "Cluster use case: Spark|Hbase|Core-Hadoop") master.NodeRole = "master" - flags.StringVar(&master.NodeType, "master-node-type", "o.hadoop2m.xlarge", "Master node type") + flags.StringVar(&master.NodeType, "master-node-type", "o.hadoop4m.xlarge", "Master node type") flags.IntVar(&master.Count, "master-count", 2, "Master node count") flags.IntVar(&master.DataDiskSize, "master-data-disk-size", 100, "Master data disk GB") flags.IntVar(&master.DataDiskNum, "master-data-disk-num", 1, "Master data disk num") diff --git a/products/uhadoop/internal/uhadoop/upgrade_disk.go b/products/uhadoop/internal/uhadoop/upgrade_disk.go index 48e994d27d..d13aaa8ba2 100644 --- a/products/uhadoop/internal/uhadoop/upgrade_disk.go +++ b/products/uhadoop/internal/uhadoop/upgrade_disk.go @@ -22,7 +22,6 @@ func newUpgradeDisk(ctx *cli.Context) *cobra.Command { Long: `Upgrade UHadoop node data disk (and optionally boot disk) size`, SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { - w := ctx.ProgressWriter() ok, err := ctx.Confirm(*yes, fmt.Sprintf("Upgrade disk on %s nodes of cluster %s to %d GB?", *req.NodeRole, *req.InstanceId, *req.DataDiskSize)) if err != nil { ctx.HandleError(err) @@ -32,17 +31,11 @@ func newUpgradeDisk(ctx *cli.Context) *cobra.Command { return } req.NodeNames = nodeNames - resp, err := client.UpgradeUHadoopNodeDisk(req) + _, err = client.UpgradeUHadoopNodeDisk(req) if err != nil { ctx.HandleError(err) return } - if resp.RetCode != 0 { - ctx.HandleError(fmt.Errorf("[%d] %s", resp.RetCode, resp.Message)) - return - } - text := fmt.Sprintf("uhadoop[%s] upgrading disk on %s nodes", *req.InstanceId, *req.NodeRole) - ctx.PollerTo(w, describeClusterForPoll(ctx, client)).Spoll(*req.InstanceId, text, []string{StateRunning}) ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceId, Action: "upgrade-disk", Status: "Upgrading"}) }, } diff --git a/products/uhadoop/internal/uhadoop/upgrade_node.go b/products/uhadoop/internal/uhadoop/upgrade_node.go index 96479ae38b..88e875ad6a 100644 --- a/products/uhadoop/internal/uhadoop/upgrade_node.go +++ b/products/uhadoop/internal/uhadoop/upgrade_node.go @@ -2,6 +2,7 @@ package uhadoop import ( "fmt" + "time" "github.com/spf13/cobra" @@ -45,7 +46,7 @@ func newUpgradeNode(ctx *cli.Context) *cobra.Command { if *async { fmt.Fprintln(w, text) } else { - ctx.PollerTo(w, describeClusterForPoll(ctx, client)).Spoll(*req.InstanceId, text, []string{StateRunning}) + ctx.PollerTo(w, describeClusterForPoll(ctx, client), cli.WithTimeout(40*time.Minute)).Spoll(*req.InstanceId, text, []string{StateRunning}) } ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceId, Action: "upgrade-node", Status: "Upgrading"}) }, diff --git a/products/uhadoop/product.yaml b/products/uhadoop/product.yaml index 1d51942310..d4caea376f 100644 --- a/products/uhadoop/product.yaml +++ b/products/uhadoop/product.yaml @@ -1,6 +1,6 @@ name: uhadoop owners: - - mark.wei@ucloud.cn + - markwei-ucloud commands: - uhadoop enabled: true From 8559acf573cdbb4ec76512bc99864750a4343954 Mon Sep 17 00:00:00 2001 From: "mark.wei" Date: Tue, 14 Jul 2026 18:37:10 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E6=A0=B9=E6=8D=AEcommnet=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- products/uhadoop/TEST.md | 47 ++++++++------- products/uhadoop/internal/uhadoop/add_node.go | 5 +- products/uhadoop/internal/uhadoop/create.go | 2 +- products/uhadoop/internal/uhadoop/delete.go | 44 +++++++------- products/uhadoop/internal/uhadoop/describe.go | 26 +------- products/uhadoop/internal/uhadoop/list.go | 3 +- .../internal/uhadoop/list_framework_app.go | 20 ------- .../internal/uhadoop/list_node_type.go | 9 --- products/uhadoop/internal/uhadoop/poll.go | 26 ++++++++ .../internal/uhadoop/restart_service.go | 60 ++++++++----------- products/uhadoop/internal/uhadoop/rows.go | 5 +- products/uhadoop/internal/uhadoop/status.go | 3 + .../uhadoop/internal/uhadoop/upgrade_disk.go | 2 +- .../uhadoop/internal/uhadoop/upgrade_node.go | 39 +++++------- products/uhadoop/testdata/cmdtree.golden | 11 ++-- products/uhadoop/testdata/completion.golden | 3 + 16 files changed, 134 insertions(+), 171 deletions(-) create mode 100644 products/uhadoop/internal/uhadoop/poll.go create mode 100644 products/uhadoop/internal/uhadoop/status.go diff --git a/products/uhadoop/TEST.md b/products/uhadoop/TEST.md index 6d2b9fd0f2..27591f55ab 100644 --- a/products/uhadoop/TEST.md +++ b/products/uhadoop/TEST.md @@ -9,11 +9,8 @@ ucloud config list # 2. 查看可用地域 ucloud region -# 3. 切换 Go 版本 -g use 1.25.12 -alias go=/Users/user/.g/go/bin/go -# 4. 替换测试参数 +# 3. 替换测试参数 # --region cn-bj2 --zone cn-bj2-02 ``` @@ -68,7 +65,7 @@ go run . uhadoop describe --region --zone --output | `--framework` | ✅ | Hadoop / HDFS / MR / StarRocks-* | | `--framework-version` | ✅ | 如 `3.3.4-udh3.2` | | `--password` | ✅ | 登录密码(明文,自动 base64) | -| `--master-node-type` | | Master 机型,默认 `o.hadoop2m.xlarge` | +| `--master-node-type` | | Master 机型,默认 `o.hadoop4m.xlarge` | | `--core-node-type` | | Core 机型,默认 `o.hadoop2m.xlarge` | | `--task-node-type` | | Task 机型(可选) | | `--master-count` | | 默认 2(StarRocks 默认 3) | @@ -76,6 +73,10 @@ go run . uhadoop describe --region --zone --output | `--task-count` | | 默认 0 | | `--cluster-case` | | Spark / Hbase / Core-Hadoop | | `--app-config` | | 手动指定组件,格式 `App#Version` | +| `--master-boot-disk-size` | | 系统盘 GB,默认 50 | +| `--master-data-disk-size` | | 数据盘 GB,默认 100 | +| `--core-boot-disk-size` | | 系统盘 GB,默认 50 | +| `--core-data-disk-size` | | 数据盘 GB,默认 200 | | `--vpc-id` | | 可选 | | `--subnet-id` | | 可选 | | `--async` | | 异步模式,不等待创建完成 | @@ -132,6 +133,9 @@ go run . uhadoop delete --region --zone --release- | `--node-role` | ✅ | core / task / client | | `--node-type` | ✅ | 机型 | | `--node-count` | | 默认 1 | +| `--password` | | Client 角色需要(明文) | +| `--boot-disk-size` | | 系统盘 GB,默认 50 | +| `--data-disk-size` | | 数据盘 GB,默认 200 | | `--async` | | 异步模式 | ```bash @@ -165,10 +169,9 @@ go run . uhadoop restart-service \ | `--region` | ✅ | 地域 | | `--zone` | ✅ | 可用区 | | `--instance-id` | ✅ | 集群 ID | -| `--node-role` | ✅ | master / core / task | +| `--node-role` | ✅ | master / core / task / client | | `--node-type` | ✅ | 新机型 | | `--node-name` | | 节点名(非 master 必填) | -| `--async` | | 异步模式 | | `--yes` / `-y` | | 跳过确认 | ```bash @@ -185,7 +188,7 @@ go run . uhadoop upgrade-node \ | `--region` | ✅ | 地域 | | `--zone` | ✅ | 可用区 | | `--instance-id` | ✅ | 集群 ID | -| `--node-role` | ✅ | master / core / task | +| `--node-role` | ✅ | master / core / task / client | | `--data-disk-size` | ✅ | 新数据盘大小 GB | | `--boot-disk-size` | | 新系统盘大小 GB | | `--node-name` | | 节点名(非 master 必填) | @@ -220,19 +223,19 @@ go run . uhadoop create --name test | # | 命令 | 状态 | |---|------|------| -| 1 | `list-framework-app` | ⬜ | -| 2 | `list-node-type` | ⬜ | -| 3 | `list` | ⬜ | -| 4 | `describe` | ⬜ | -| 5 | `create --cluster-case Spark` | ⬜ | -| 6 | `create --app-config ...` | ⬜ | -| 7 | `delete` | ⬜ | -| 8 | `add-node` | ⬜ | -| 9 | `restart-service` | ⬜ | -| 10 | `upgrade-node` | ⬜ | -| 11 | `upgrade-disk` | ⬜ | -| 12 | `create` 缺参 | ⬜ | -| 13 | `go build ./...` | ⬜ | -| 14 | `go test ./hack/snapshot -v` | ⬜ | +| 1 | `list-framework-app` | ✅ | +| 2 | `list-node-type` | ✅ | +| 3 | `list` | ✅ | +| 4 | `describe` | ✅ | +| 5 | `create --cluster-case Spark` | ✅ | +| 6 | `create --app-config ...` | ✅ | +| 7 | `delete` | ✅ | +| 8 | `add-node` | ✅ | +| 9 | `restart-service` | ✅ | +| 10 | `upgrade-node` | ✅ | +| 11 | `upgrade-disk` | ✅ | +| 12 | `create` 缺参 | ✅ | +| 13 | `go build ./...` | ✅ | +| 14 | `go test ./hack/snapshot -v` | ✅ | > ⬜ = 待测试   ✅ = 通过   ❌ = 失败 diff --git a/products/uhadoop/internal/uhadoop/add_node.go b/products/uhadoop/internal/uhadoop/add_node.go index 63b5e6cefc..4619f8108c 100644 --- a/products/uhadoop/internal/uhadoop/add_node.go +++ b/products/uhadoop/internal/uhadoop/add_node.go @@ -6,6 +6,7 @@ import ( "time" "github.com/spf13/cobra" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" @@ -28,7 +29,7 @@ func newAddNode(ctx *cli.Context) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { w := ctx.ProgressWriter() if rawPassword != "" { - req.Password = sdkStr(base64.StdEncoding.EncodeToString([]byte(rawPassword))) + req.Password = sdk.String(base64.StdEncoding.EncodeToString([]byte(rawPassword))) } resp, err := client.AddUHadoopInstanceNode(req) if err != nil { @@ -43,7 +44,7 @@ func newAddNode(ctx *cli.Context) *cobra.Command { if *async { fmt.Fprintln(w, text) } else { - ctx.PollerTo(w, describeClusterForPoll(ctx, client), cli.WithTimeout(60*time.Minute)).Spoll(*req.InstanceId, text, []string{StateRunning}) + ctx.PollerTo(w, describeClusterForPoll(ctx, client), cli.WithTimeout(60*time.Minute)).Spoll(*req.InstanceId, text, []string{stateRunning}) } ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceId, Action: "add-node", Status: "Scaling"}) }, diff --git a/products/uhadoop/internal/uhadoop/create.go b/products/uhadoop/internal/uhadoop/create.go index 81e5d93d7b..13a12a0f75 100644 --- a/products/uhadoop/internal/uhadoop/create.go +++ b/products/uhadoop/internal/uhadoop/create.go @@ -208,7 +208,7 @@ func newCreate(ctx *cli.Context) *cobra.Command { if *async { fmt.Fprintln(w, text) } else { - ctx.PollerTo(w, describeClusterForPoll(ctx, client), cli.WithTimeout(60*time.Minute)).Spoll(resp.InstanceId, text, []string{StateRunning}) + ctx.PollerTo(w, describeClusterForPoll(ctx, client), cli.WithTimeout(60*time.Minute)).Spoll(resp.InstanceId, text, []string{stateRunning}) } ctx.EmitResult(cli.OpResultRow{ResourceID: resp.InstanceId, Action: "create", Status: "Creating"}) }, diff --git a/products/uhadoop/internal/uhadoop/delete.go b/products/uhadoop/internal/uhadoop/delete.go index 74b00bffd7..d9101de96c 100644 --- a/products/uhadoop/internal/uhadoop/delete.go +++ b/products/uhadoop/internal/uhadoop/delete.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/spf13/cobra" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" @@ -11,50 +12,47 @@ import ( "github.com/ucloud/ucloud-cli/pkg/command" ) -// newDelete ucloud uhadoop delete func newDelete(ctx *cli.Context) *cobra.Command { var yes bool client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewDeleteUHadoopInstanceRequest() cmd := &cobra.Command{ - Use: "delete ", - Short: "Delete a UHadoop cluster", - Long: `Delete a UHadoop cluster by instance ID`, - Args: cobra.ExactArgs(1), - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { + Use: "delete ", + Short: "Delete a UHadoop cluster", + Long: `Delete a UHadoop cluster by instance ID`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { id := args[0] ok, err := ctx.Confirm(yes, fmt.Sprintf("Are you sure you want to delete cluster %s?", id)) if err != nil { - return err + ctx.HandleError(err) + return } if !ok { - return nil + return } - req.InstanceId = sdkStr(id) - resp, err := client.DeleteUHadoopInstance(req) + w := ctx.ProgressWriter() + req.InstanceId = sdk.String(id) + _, err = client.DeleteUHadoopInstance(req) if err != nil { - return err + ctx.HandleError(err) + return } - if resp.RetCode != 0 { - return fmt.Errorf("[%d] %s", resp.RetCode, resp.Message) - } - fmt.Fprintf(ctx.Err(), "Cluster %s deleted\n", id) - ctx.PrintJSON(resp) - return nil + fmt.Fprintf(w, "uhadoop[%s] deleted\n", id) + ctx.EmitResult(cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) }, } - cmd.Flags().SortFlags = false - + flags := cmd.Flags() + flags.SortFlags = false + flags.BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation") + req.ReleaseEIP = flags.Bool("release-eip", false, "Optional. Release bound EIP after deletion") ctx.BindRegion(cmd, req) ctx.BindZone(cmd, req) ctx.BindProjectID(cmd, req) - cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation.") - req.ReleaseEIP = cmd.Flags().Bool("release-eip", false, "Optional. Release bound EIP after deletion") command.SetFlagValues(cmd, "release-eip", "true", "false") - cmd.MarkFlagRequired("region") cmd.MarkFlagRequired("zone") + return cmd } diff --git a/products/uhadoop/internal/uhadoop/describe.go b/products/uhadoop/internal/uhadoop/describe.go index 028b90c3fc..22cf12ed6c 100644 --- a/products/uhadoop/internal/uhadoop/describe.go +++ b/products/uhadoop/internal/uhadoop/describe.go @@ -3,17 +3,14 @@ package uhadoop import ( "github.com/spf13/cobra" - "github.com/ucloud/ucloud-sdk-go/ucloud/request" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" "github.com/ucloud/ucloud-sdk-go/ucloud/response" uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" "github.com/ucloud/ucloud-cli/pkg/cli" - "github.com/ucloud/ucloud-cli/pkg/command" ) -const StateRunning = "Running" - type describeClusterResponse struct { response.CommonBase ClusterSet []describeClusterInfo `json:"ClusterSet"` @@ -62,7 +59,7 @@ func newDescribe(ctx *cli.Context) *cobra.Command { SilenceUsage: true, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - req.InstanceId = sdkStr(args[0]) + req.InstanceId = sdk.String(args[0]) var resp describeClusterResponse err := client.InvokeAction("DescribeUHadoopInstance", req, &resp) if err != nil { @@ -75,26 +72,7 @@ func newDescribe(ctx *cli.Context) *cobra.Command { cmd.Flags().SortFlags = false ctx.BindRegion(cmd, req) ctx.BindZone(cmd, req) - command.SetFlagValues(cmd, "region", ctx.RegionList()...) cmd.MarkFlagRequired("region") cmd.MarkFlagRequired("zone") return cmd } - -func sdkStr(s string) *string { return &s } - -func describeClusterForPoll(ctx *cli.Context, client *uhadoopsdk.UHadoopClient) func(string, *request.CommonBase) (interface{}, error) { - return func(id string, _ *request.CommonBase) (interface{}, error) { - req := client.NewDescribeUHadoopInstanceRequest() - req.InstanceId = sdkStr(id) - var resp describeClusterResponse - err := client.InvokeAction("DescribeUHadoopInstance", req, &resp) - if err != nil { - return nil, err - } - if len(resp.ClusterSet) == 0 { - return nil, nil - } - return resp.ClusterSet[0], nil - } -} diff --git a/products/uhadoop/internal/uhadoop/list.go b/products/uhadoop/internal/uhadoop/list.go index 9592cf4d45..8218c10e0b 100644 --- a/products/uhadoop/internal/uhadoop/list.go +++ b/products/uhadoop/internal/uhadoop/list.go @@ -91,8 +91,6 @@ func newList(ctx *cli.Context) *cobra.Command { command.SetFlagValues(cmd, "all-region", "true", "false") command.SetFlagValues(cmd, "id-only", "true", "false") - cmd.MarkFlagRequired("region") - cmd.MarkFlagRequired("zone") return cmd } @@ -106,6 +104,7 @@ func getAllClusters(ctx *cli.Context, client *uhadoopsdk.UHadoopClient, req *uha for _, region := range regions { _req := *req _req.Region = sdk.String(region) + _req.Zone = nil // clear zone when fanning out across regions clusters, err := fetchClustersPageOff(client, &_req) if e, ok := err.(sdkerror.Error); ok && e.Code() == _RetCodeRegionNoPermission { continue diff --git a/products/uhadoop/internal/uhadoop/list_framework_app.go b/products/uhadoop/internal/uhadoop/list_framework_app.go index b7b42e9768..daac63d2af 100644 --- a/products/uhadoop/internal/uhadoop/list_framework_app.go +++ b/products/uhadoop/internal/uhadoop/list_framework_app.go @@ -1,7 +1,6 @@ package uhadoop import ( - "fmt" "strings" "github.com/spf13/cobra" @@ -52,14 +51,6 @@ func newListFrameworkApp(ctx *cli.Context) *cobra.Command { Long: `List available UHadoop frameworks and their applications organized by use case`, SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { - if req.Region == nil || *req.Region == "" { - ctx.HandleError(fmt.Errorf("--region is required")) - return - } - if req.Zone == nil || *req.Zone == "" { - ctx.HandleError(fmt.Errorf("--zone is required")) - return - } var resp frameworkAppResponse // Use InvokeAction directly with our custom response struct because @@ -112,14 +103,3 @@ func listFrameworkApps(ctx *cli.Context, appConfigs []frameworkAppConfigVersion) } ctx.PrintList(list) } - -type frameworkRow struct { - Framework string - FrameworkVersion string - ReleaseVersion string - HadoopVersion string - UseCase string - Apps string - Versions string - MustHas string -} diff --git a/products/uhadoop/internal/uhadoop/list_node_type.go b/products/uhadoop/internal/uhadoop/list_node_type.go index 198a0f29f2..10b5a3c16f 100644 --- a/products/uhadoop/internal/uhadoop/list_node_type.go +++ b/products/uhadoop/internal/uhadoop/list_node_type.go @@ -1,7 +1,6 @@ package uhadoop import ( - "fmt" "strings" "github.com/spf13/cobra" @@ -22,14 +21,6 @@ func newListNodeType(ctx *cli.Context) *cobra.Command { Long: `List available node/instance types for UHadoop clusters`, SilenceUsage: true, Run: func(cmd *cobra.Command, args []string) { - if req.Region == nil || *req.Region == "" { - ctx.HandleError(fmt.Errorf("--region is required")) - return - } - if req.Zone == nil || *req.Zone == "" { - ctx.HandleError(fmt.Errorf("--zone is required")) - return - } resp, err := client.GetUHadoopNodeType(req) if err != nil { diff --git a/products/uhadoop/internal/uhadoop/poll.go b/products/uhadoop/internal/uhadoop/poll.go new file mode 100644 index 0000000000..02fe71874b --- /dev/null +++ b/products/uhadoop/internal/uhadoop/poll.go @@ -0,0 +1,26 @@ +package uhadoop + +import ( + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + uhadoopsdk "github.com/ucloud/ucloud-sdk-go/services/uhadoop" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func describeClusterForPoll(ctx *cli.Context, client *uhadoopsdk.UHadoopClient) func(string, *request.CommonBase) (interface{}, error) { + return func(id string, _ *request.CommonBase) (interface{}, error) { + req := client.NewDescribeUHadoopInstanceRequest() + req.InstanceId = sdk.String(id) + var resp describeClusterResponse + err := client.InvokeAction("DescribeUHadoopInstance", req, &resp) + if err != nil { + return nil, err + } + if len(resp.ClusterSet) == 0 { + return nil, nil + } + return resp.ClusterSet[0], nil + } +} diff --git a/products/uhadoop/internal/uhadoop/restart_service.go b/products/uhadoop/internal/uhadoop/restart_service.go index ee29477e82..f336baa353 100644 --- a/products/uhadoop/internal/uhadoop/restart_service.go +++ b/products/uhadoop/internal/uhadoop/restart_service.go @@ -11,7 +11,6 @@ import ( "github.com/ucloud/ucloud-cli/pkg/command" ) -// newRestartService ucloud uhadoop restart-service func newRestartService(ctx *cli.Context) *cobra.Command { var yes bool client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) @@ -19,18 +18,10 @@ func newRestartService(ctx *cli.Context) *cobra.Command { var nodeIds []string var nodeRoles []string cmd := &cobra.Command{ - Use: "restart-service", - Short: "Restart/start/stop a UHadoop cluster service", - Long: `Restart, start, or stop a service on a UHadoop cluster`, - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - if req.InstanceId == nil || *req.InstanceId == "" { - return fmt.Errorf("--instance-id is required") - } - if req.ServiceName == nil || *req.ServiceName == "" { - return fmt.Errorf("--service-name is required") - } - + Use: "restart-service", + Short: "Restart/start/stop a UHadoop cluster service", + Long: `Restart, start, or stop a service on a UHadoop cluster`, + Run: func(cmd *cobra.Command, args []string) { action := "restart" if req.OnlyStart != nil && *req.OnlyStart { action = "start" @@ -38,48 +29,47 @@ func newRestartService(ctx *cli.Context) *cobra.Command { if req.OnlyStop != nil && *req.OnlyStop { action = "stop" } - ok, err := ctx.Confirm(yes, fmt.Sprintf("Are you sure you want to %s service %s on cluster %s?", action, *req.ServiceName, *req.InstanceId)) if err != nil { - return err + ctx.HandleError(err) + return } if !ok { - return nil + return } - + w := ctx.ProgressWriter() req.NodeId = nodeIds req.NodeRole = nodeRoles resp, err := client.RestartUHadoopService(req) if err != nil { - return err + ctx.HandleError(err) + return } - if resp.RetCode != 0 { - return fmt.Errorf("[%d] %s", resp.RetCode, resp.Message) - } - fmt.Fprintf(ctx.Err(), "Service %s %s on cluster %s, state: %s\n", *req.ServiceName, action, *req.InstanceId, resp.State) - ctx.PrintJSON(resp) - return nil + fmt.Fprintf(w, "uhadoop[%s] service %s %s, state: %s\n", *req.InstanceId, *req.ServiceName, action, resp.State) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceId, Action: action, Status: resp.State}) }, } - cmd.Flags().SortFlags = false - + flags := cmd.Flags() + flags.SortFlags = false ctx.BindRegion(cmd, req) ctx.BindZone(cmd, req) ctx.BindProjectID(cmd, req) - req.InstanceId = cmd.Flags().String("instance-id", "", "Required. Cluster instance ID") - req.ServiceName = cmd.Flags().String("service-name", "", "Required. Service name (e.g. Hive, Spark, Hdfs, Yarn)") - req.ApplicationVersion = cmd.Flags().String("application-version", "", "Optional. Application version, if set, operates on all services of the app") - req.OnlyStart = cmd.Flags().Bool("only-start", false, "Optional. Only start the service") - req.OnlyStop = cmd.Flags().Bool("only-stop", false, "Optional. Only stop the service") - cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation.") - cmd.Flags().StringSliceVar(&nodeIds, "node-id", nil, "Optional. Node IDs to filter, can be specified multiple times") - cmd.Flags().StringSliceVar(&nodeRoles, "node-role", nil, "Optional. Node roles to filter: master|core|task, can be specified multiple times") + req.InstanceId = flags.String("instance-id", "", "Required. Cluster instance ID") + req.ServiceName = flags.String("service-name", "", "Required. Service name") + req.ApplicationVersion = flags.String("application-version", "", "Optional. Application version") + req.OnlyStart = flags.Bool("only-start", false, "Optional. Only start the service") + req.OnlyStop = flags.Bool("only-stop", false, "Optional. Only stop the service") + flags.BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation") + flags.StringSliceVar(&nodeIds, "node-id", nil, "Optional. Node IDs") + flags.StringSliceVar(&nodeRoles, "node-role", nil, "Optional. Node roles: master|core|task") command.SetFlagValues(cmd, "only-start", "true", "false") command.SetFlagValues(cmd, "only-stop", "true", "false") command.SetFlagValues(cmd, "node-role", "master", "core", "task") - + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("service-name") cmd.MarkFlagRequired("region") cmd.MarkFlagRequired("zone") + return cmd } diff --git a/products/uhadoop/internal/uhadoop/rows.go b/products/uhadoop/internal/uhadoop/rows.go index c41171779e..cedd142968 100644 --- a/products/uhadoop/internal/uhadoop/rows.go +++ b/products/uhadoop/internal/uhadoop/rows.go @@ -47,13 +47,14 @@ type instanceTypeRow struct { DiskMaxNum string } -// frameworkAppRow is the row for ListUHadoopFrameworkAppByUseCase output. -type frameworkAppRow struct { +// frameworkRow is the row for ListUHadoopFrameworkAppByUseCase output. +type frameworkRow struct { Framework string FrameworkVersion string ReleaseVersion string HadoopVersion string UseCase string Apps string + Versions string MustHas string } diff --git a/products/uhadoop/internal/uhadoop/status.go b/products/uhadoop/internal/uhadoop/status.go new file mode 100644 index 0000000000..0f6204686e --- /dev/null +++ b/products/uhadoop/internal/uhadoop/status.go @@ -0,0 +1,3 @@ +package uhadoop + +const stateRunning = "Running" diff --git a/products/uhadoop/internal/uhadoop/upgrade_disk.go b/products/uhadoop/internal/uhadoop/upgrade_disk.go index d13aaa8ba2..bca5228601 100644 --- a/products/uhadoop/internal/uhadoop/upgrade_disk.go +++ b/products/uhadoop/internal/uhadoop/upgrade_disk.go @@ -45,7 +45,7 @@ func newUpgradeDisk(ctx *cli.Context) *cobra.Command { req.Zone = flags.String("zone", "", "Optional. Assign availability zone") req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") req.InstanceId = flags.String("instance-id", "", "Required. Cluster instance ID") - req.NodeRole = flags.String("node-role", "", "Required. Node role: master|core|task") + req.NodeRole = flags.String("node-role", "", "Required. Node role: master|core|task|client") req.DataDiskSize = flags.Int("data-disk-size", 0, "Required. New data disk size in GB") req.BootDiskSize = flags.Int("boot-disk-size", 0, "Optional. New boot disk size in GB") yes = flags.BoolP("yes", "y", false, "Do not prompt for confirmation") diff --git a/products/uhadoop/internal/uhadoop/upgrade_node.go b/products/uhadoop/internal/uhadoop/upgrade_node.go index 88e875ad6a..14aa5fd45b 100644 --- a/products/uhadoop/internal/uhadoop/upgrade_node.go +++ b/products/uhadoop/internal/uhadoop/upgrade_node.go @@ -13,18 +13,16 @@ import ( ) func newUpgradeNode(ctx *cli.Context) *cobra.Command { - var yes, async *bool + var yes bool client := cli.NewServiceClient(ctx, uhadoopsdk.NewClient) req := client.NewUpgradeUHadoopNodeRequest() var nodeNames []string cmd := &cobra.Command{ - Use: "upgrade-node", - Short: "Upgrade UHadoop node instance type", - Long: `Upgrade UHadoop node to a new instance type`, - SilenceUsage: true, + Use: "upgrade-node", + Short: "Upgrade UHadoop node instance type", + Long: `Upgrade UHadoop node to a new instance type`, Run: func(cmd *cobra.Command, args []string) { - w := ctx.ProgressWriter() - ok, err := ctx.Confirm(*yes, fmt.Sprintf("Upgrade %s nodes on cluster %s to %s?", *req.NodeRole, *req.InstanceId, *req.NodeType)) + ok, err := ctx.Confirm(yes, fmt.Sprintf("Upgrade %s nodes on cluster %s to %s?", *req.NodeRole, *req.InstanceId, *req.NodeType)) if err != nil { ctx.HandleError(err) return @@ -32,35 +30,28 @@ func newUpgradeNode(ctx *cli.Context) *cobra.Command { if !ok { return } + w := ctx.ProgressWriter() req.NodeNames = nodeNames - resp, err := client.UpgradeUHadoopNode(req) + _, err = client.UpgradeUHadoopNode(req) if err != nil { ctx.HandleError(err) return } - if resp.RetCode != 0 { - ctx.HandleError(fmt.Errorf("[%d] %s", resp.RetCode, resp.Message)) - return - } - text := fmt.Sprintf("uhadoop[%s] upgrading %s nodes", *req.InstanceId, *req.NodeRole) - if *async { - fmt.Fprintln(w, text) - } else { - ctx.PollerTo(w, describeClusterForPoll(ctx, client), cli.WithTimeout(40*time.Minute)).Spoll(*req.InstanceId, text, []string{StateRunning}) - } + text := fmt.Sprintf("uhadoop[%s] upgrading %s nodes to %s", *req.InstanceId, *req.NodeRole, *req.NodeType) + fmt.Fprintln(w, text) + ctx.PollerTo(w, describeClusterForPoll(ctx, client), cli.WithTimeout(40*time.Minute)).Spoll(*req.InstanceId, text, []string{stateRunning}) ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceId, Action: "upgrade-node", Status: "Upgrading"}) }, } flags := cmd.Flags() flags.SortFlags = false - req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") - req.Zone = flags.String("zone", "", "Optional. Assign availability zone") - req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) req.InstanceId = flags.String("instance-id", "", "Required. Cluster instance ID") - req.NodeRole = flags.String("node-role", "", "Required. Node role: master|core|task") + req.NodeRole = flags.String("node-role", "", "Required. Node role: master|core|task|client") req.NodeType = flags.String("node-type", "", "Required. New node type") - yes = flags.BoolP("yes", "y", false, "Do not prompt for confirmation") - async = flags.Bool("async", false, "Optional. Do not wait for upgrade to complete") + flags.BoolVarP(&yes, "yes", "y", false, "Do not prompt for confirmation") flags.StringSliceVar(&nodeNames, "node-name", nil, "Node names, required when NodeRole != master") command.SetFlagValues(cmd, "node-role", "master", "core", "task", "client") cmd.MarkFlagRequired("instance-id") diff --git a/products/uhadoop/testdata/cmdtree.golden b/products/uhadoop/testdata/cmdtree.golden index e22acff82a..955457757c 100644 --- a/products/uhadoop/testdata/cmdtree.golden +++ b/products/uhadoop/testdata/cmdtree.golden @@ -35,7 +35,7 @@ ucloud uhadoop create use=create short=Create a UHadoop cluster flag=master-data-disk-num short= default=1 required= flag=master-data-disk-size short= default=100 required= flag=master-data-disk-type short= default=CLOUD_RSSD required= - flag=master-node-type short= default=o.hadoop2m.xlarge required= + flag=master-node-type short= default=o.hadoop4m.xlarge required= flag=meta-store short= default= required= flag=name short= default= required=true flag=password short= default= required=true @@ -74,8 +74,8 @@ ucloud uhadoop list use=list short=List all UHadoop clusters flag=limit short= default=60 required= flag=offset short= default=0 required= flag=project-id short= default= required= - flag=region short= default= required=true - flag=zone short= default= required=true + flag=region short= default= required= + flag=zone short= default= required= ucloud uhadoop list-framework-app use=list-framework-app short=List UHadoop framework apps by use case flag=project-id short= default= required= flag=region short= default= required=true @@ -89,14 +89,14 @@ ucloud uhadoop list-node-type use=list-node-type short=List available node types flag=zone short= default= required=true ucloud uhadoop restart-service use=restart-service short=Restart/start/stop a UHadoop cluster service flag=application-version short= default= required= - flag=instance-id short= default= required= + flag=instance-id short= default= required=true flag=node-id short= default=[] required= flag=node-role short= default=[] required= flag=only-start short= default=false required= flag=only-stop short= default=false required= flag=project-id short= default= required= flag=region short= default= required=true - flag=service-name short= default= required= + flag=service-name short= default= required=true flag=yes short=y default=false required= flag=zone short= default= required=true ucloud uhadoop upgrade-disk use=upgrade-disk short=Upgrade UHadoop node disk size @@ -110,7 +110,6 @@ ucloud uhadoop upgrade-disk use=upgrade-disk short=Upgrade UHadoop node disk siz flag=yes short=y default=false required= flag=zone short= default= required=true ucloud uhadoop upgrade-node use=upgrade-node short=Upgrade UHadoop node instance type - flag=async short= default=false required= flag=instance-id short= default= required=true flag=node-name short= default=[] required= flag=node-role short= default= required=true diff --git a/products/uhadoop/testdata/completion.golden b/products/uhadoop/testdata/completion.golden index a4603c16e1..444fa9fe5f 100644 --- a/products/uhadoop/testdata/completion.golden +++ b/products/uhadoop/testdata/completion.golden @@ -26,3 +26,6 @@ ucloud uhadoop restart-service region dynamic ucloud uhadoop restart-service zone dynamic ucloud uhadoop upgrade-disk node-role static client,core,master,task ucloud uhadoop upgrade-node node-role static client,core,master,task +ucloud uhadoop upgrade-node project-id dynamic +ucloud uhadoop upgrade-node region dynamic +ucloud uhadoop upgrade-node zone dynamic