Skip to content

Commit

Permalink
feat(modelarts): add resource to operate devserver
Browse files Browse the repository at this point in the history
  • Loading branch information
wuzhuanhong committed Dec 30, 2024
1 parent 2f59e85 commit ccd318b
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 4 deletions.
44 changes: 44 additions & 0 deletions docs/resources/modelarts_devserver_action.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
subcategory: "AI Development Platform (ModelArts)"
layout: "huaweicloud"
page_title: "HuaweiCloud: huaweicloud_modelarts_devserver_action"
description: |-
Use this resource to operate ModelArts DevServer within HuaweiCloud.
---
# huaweicloud_modelarts_devserver_action

Use this resource to operate ModelArts DevServer within HuaweiCloud.

## Example Usage

```hcl
variable "devserver_id" {}
resource "huaweicloud_modelarts_devserver_action" "test" {
devserver_id = var.devserver_id
action = "start"
}
```

## Argument Reference

The following arguments are supported:

* `region` - (Optional, String, ForceNew) Specifies the region in which to create the resource.
If omitted, the provider-level region will be used.
Changing this creates a new resource.

* `devserver_id` - (Required, String, ForceNew) Specifies the ID of the DevServer.
Changing this creates a new resource.

* `action` - (Required, String, ForceNew) Specifies the action type of the DevServer.
Changing this creates a new resource.
The valid values are as follows:
+ **start**: The DevServer can be started only when the DevServer is stopped, stop failure, or start failure.
+ **stop**: The DevServer can be stopped only when it is running or stop failure.

## Attribute Reference

In addition to all arguments above, the following attributes are exported:

* `id` - The resource ID.
1 change: 1 addition & 0 deletions huaweicloud/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1928,6 +1928,7 @@ func Provider() *schema.Provider {

"huaweicloud_modelarts_dataset": modelarts.ResourceDataset(),
"huaweicloud_modelarts_dataset_version": modelarts.ResourceDatasetVersion(),
"huaweicloud_modelarts_devserver_action": modelarts.ResourceDevServerAction(),
"huaweicloud_modelarts_devserver": modelarts.ResourceDevServer(),
"huaweicloud_modelarts_notebook": modelarts.ResourceNotebook(),
"huaweicloud_modelarts_notebook_mount_storage": modelarts.ResourceNotebookMountStorage(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestAccDevServer_basic(t *testing.T) {
CheckDestroy: rc.CheckResourceDestroy(),
Steps: []resource.TestStep{
{
Config: testDevServer_basic(name, password, true),
Config: testAccDevServer_basic(true, name, password),
Check: resource.ComposeTestCheckFunc(
rc.CheckResourceExists(),
resource.TestCheckResourceAttr(resourceName, "name", name),
Expand All @@ -64,12 +64,30 @@ func TestAccDevServer_basic(t *testing.T) {
),
},
{
Config: testDevServer_basic(name, password, false),
Config: testAccDevServer_basic(false, name, password),
Check: resource.ComposeTestCheckFunc(
rc.CheckResourceExists(),
resource.TestCheckResourceAttr(resourceName, "auto_renew", "false"),
),
},
// Stopping the DevServer.
{
Config: testAccDevServer_doAction(name, password, "stop", false),
},
// Stopping the stopped DevServer.
{
Config: testAccDevServer_doAction(name, password, "stop", true),
ExpectError: regexp.MustCompile(`Resource.Server '[a-f0-9-]+' is not allowed STOP: STOPPED`),
},
// Starting the DevServer.
{
Config: testAccDevServer_doAction(name, password, "start", false),
},
// Starting the running DevServer.
{
Config: testAccDevServer_doAction(name, password, "start", true),
ExpectError: regexp.MustCompile(`Resource.Server '[a-f0-9-]+' is not allowed START: RUNNING`),
},
{
ResourceName: resourceName,
ImportState: true,
Expand All @@ -88,7 +106,7 @@ func TestAccDevServer_basic(t *testing.T) {
})
}

func testDevServer_basic(name, password string, autoRenew bool) string {
func testAccDevServer_basic(isAutoRenew bool, name, password string) string {
return fmt.Sprintf(`
%[1]s
Expand All @@ -112,5 +130,30 @@ resource "huaweicloud_modelarts_devserver" "test" {
auto_renew = "%[6]v"
}
`, common.TestBaseNetwork(name), name,
acceptance.HW_MODELARTS_DEVSERVER_FLAVOR, acceptance.HW_MODELARTS_DEVSERVER_IMAGE_ID, password, autoRenew)
acceptance.HW_MODELARTS_DEVSERVER_FLAVOR, acceptance.HW_MODELARTS_DEVSERVER_IMAGE_ID, password, isAutoRenew)
}

func testAccDevServer_doAction(name, password, actionType string, doRetryAction bool) string {
return fmt.Sprintf(`
%[1]s
resource "huaweicloud_modelarts_devserver_action" "test" {
devserver_id = huaweicloud_modelarts_devserver.test.id
action = "%[2]s"
}
variable "is_retry_devserver_action" {
type = bool
default = "%[3]v"
}
resource "huaweicloud_modelarts_devserver_action" "expect_err" {
count = var.is_retry_devserver_action ? 1 : 0
depends_on = [huaweicloud_modelarts_devserver_action.test]
devserver_id = huaweicloud_modelarts_devserver.test.id
action = "%[2]s"
}
`, testAccDevServer_basic(false, name, password), actionType, doRetryAction)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package modelarts

import (
"context"
"fmt"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/chnsz/golangsdk"

"github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config"
"github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils"
)

// @API ModelArts PUT /v1/{project_id}/dev-servers/{id}/start
// @API ModelArts PUT /v1/{project_id}/dev-servers/{id}/stop
// @API ModelArts GET /v1/{project_id}/dev-servers/{id}
func ResourceDevServerAction() *schema.Resource {
return &schema.Resource{
CreateContext: resourceDevServerActionCreate,
ReadContext: resourceDevServerActionRead,
DeleteContext: resourceDevServerActionDelete,

Schema: map[string]*schema.Schema{
"region": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"devserver_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: `The ID of the DevServer.`,
},
"action": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: `The action type of the DevServer.`,
},
},
}
}

func resourceDevServerActionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var (
cfg = meta.(*config.Config)
region = cfg.GetRegion(d)
httpUrl = "v1/{project_id}/dev-servers/{id}/{action}"
devServerId = d.Get("devserver_id").(string)
action = d.Get("action").(string)
actionCompletedMap = map[string]string{
"start": "RUNNING",
"stop": "STOPPED",
}
)

client, err := cfg.NewServiceClient("modelarts", region)
if err != nil {
return diag.Errorf("error creating ModelArts client: %s", err)
}

actionPath := client.Endpoint + httpUrl
actionPath = strings.ReplaceAll(actionPath, "{project_id}", client.ProjectID)
actionPath = strings.ReplaceAll(actionPath, "{id}", devServerId)
actionPath = strings.ReplaceAll(actionPath, "{action}", action)
actionOpt := golangsdk.RequestOpts{
KeepResponseBody: true,
}
if action == "start" {
actionOpt.JSONBody = map[string]interface{}{}
}

_, err = client.Request("PUT", actionPath, &actionOpt)
if err != nil {
return diag.Errorf("unable to %s DevServer (%s): %s", action, devServerId, err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{"PENDING"},
Target: []string{"COMPLETED"},
Refresh: refreshDevServerActionStatusFunc(client, devServerId, actionCompletedMap[action]),
Timeout: d.Timeout(schema.TimeoutCreate),
Delay: 10 * time.Second,
PollInterval: 10 * time.Second,
}
_, err = stateConf.WaitForStateContext(ctx)
if err != nil {
return diag.Errorf("error waiting for DevServer (%s) to %s completed: %s", devServerId, action, err)
}

d.SetId(devServerId)

return nil
}

func refreshDevServerActionStatusFunc(client *golangsdk.ServiceClient, devServerId string, target string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
respBody, err := GetDevServerById(client, devServerId)
if err != nil {
return nil, "ERROR", err
}

status := utils.PathSearch("status", respBody, "").(string)
if utils.StrSliceContains([]string{"START_FAILED", "ERROR", "STOP_FAILED"}, status) {
return respBody, "ERROR", fmt.Errorf("unexpected status (%s)", status)
}

if status == target {
return respBody, "COMPLETED", nil
}
return "continue", "PENDING", nil
}
}

func resourceDevServerActionRead(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
return nil
}

func resourceDevServerActionDelete(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
errorMsg := `This resource is only a one-time action resource for operating the DevServer. Deleting this resource will
not clear the corresponding request record, but will only remove the resource information from the tfstate file.`
return diag.Diagnostics{
diag.Diagnostic{
Severity: diag.Warning,
Summary: errorMsg,
},
}
}

0 comments on commit ccd318b

Please sign in to comment.