Skip to content

Commit

Permalink
feat: fix(modelarts): support a new parameter and support the scope w…
Browse files Browse the repository at this point in the history
…aiting (#4945)

* feat(modelarts/notebook): volume structure support id param

* fix(modelarts/pool): needs to wait for the scope statuses to be complete
  • Loading branch information
Lance52259 authored May 31, 2024
1 parent 69a23f7 commit d0a7418
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 25 deletions.
47 changes: 41 additions & 6 deletions docs/resources/modelarts_notebook.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,52 @@ Manages ModelArts notebook resource within HuaweiCloud.

## Example Usage

### Create a notebook with the EVS storage type

```hcl
variable "notebook_name" {}
variable "key_pair_name" {}
variable "ip" {}
variable "image_id" {}
variable "allowed_ip_addresses" {
type = list(string)
}
variable "key_pair_name" {}
resource "huaweicloud_modelarts_notebook" "notebook" {
resource "huaweicloud_modelarts_notebook" "test" {
name = var.notebook_name
flavor_id = "modelarts.vm.cpu.2u"
image_id = "e1a07296-22a8-4f05-8bc8-e936c8e54090"
image_id = var.image_id
allowed_access_ips = [var.ip]
allowed_access_ips = var.allowed_ip_addresses
key_pair = var.key_pair_name
volume {
type = "EFS"
type = "EVS"
size = 5
}
}
```

### Create a notebook with the EFS storage type

```hcl
variable "notebook_name" {}
variable "image_id" {}
variable "resource_pool_id" {}
variable "sfs_export_location" {}
variable "sfs_turbo_id" {}
resource "huaweicloud_modelarts_notebook" "test" {
name = var.notebook_name
flavor_id = "modelarts.vm.cpu.2u"
image_id = var.image_id
pool_id = var.resource_pool_id
volume {
type = "EFS"
ownership = "DEDICATED"
uri = var.sfs_export_location
id = var.sfs_turbo_id
}
}
```
Expand Down Expand Up @@ -89,10 +120,14 @@ The `volume` block supports:

Changing this parameter will create a new resource.

* `uri` - (Optional, String, ForceNew) Specifies the uri of dedicated storage disk, which is mandatory when the `type`
* `uri` - (Optional, String, ForceNew) Specifies the URL of dedicated storage disk, which is mandatory when the `type`
is `EFS` and the `ownership` is `DEDICATED`. Example: `192.168.0.1:/user-9sfdsdgdfgh5ea4d56871e75d6966aa274/mount/`.
Changing this parameter will create a new resource.

* `id` - (Optional, String, ForceNew) Specifies the ID of dedicated storage disk, which is mandatory when the `type`
is `EFS` and the `ownership` is `DEDICATED`.
Changing this parameter will create a new resource.

## Attribute Reference

In addition to all arguments above, the following attributes are exported:
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/modelarts_resource_pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ The following arguments are supported:
Including resource flavors and the number of resources of the corresponding flavors.
The [resources](#ModelartsResourcePool_ResourceFlavor) structure is documented below.

* `scope` - (Optional, List) Specifies the list of job types supported by the resource pool. It is mandatory when
* `scope` - (Required, List) Specifies the list of job types supported by the resource pool. It is mandatory when
`network_id` is specified and can not be specified when `vpc_id` is specified. The options are as follows:
+ **Train**: training job.
+ **Infer**: inference job.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.18

require (
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
github.com/chnsz/golangsdk v0.0.0-20240529073340-b68ab4ec7a36
github.com/chnsz/golangsdk v0.0.0-20240531093804-71e344f541e8
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-uuid v1.0.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chnsz/golangsdk v0.0.0-20240529073340-b68ab4ec7a36 h1:MiPVVsb+UjJOJa1Jydc0QCPrX1RIVrqXI8nqj1Yz6X4=
github.com/chnsz/golangsdk v0.0.0-20240529073340-b68ab4ec7a36/go.mod h1:Erm4hDWxXgAdbkG3+hhJFgRzEL1TvvcroWzw2Gax4uI=
github.com/chnsz/golangsdk v0.0.0-20240531093804-71e344f541e8 h1:TFPjAfOsUpO6UZFfDoYImYpchjbPBjMgtAPu5UJ8Zpg=
github.com/chnsz/golangsdk v0.0.0-20240531093804-71e344f541e8/go.mod h1:Erm4hDWxXgAdbkG3+hhJFgRzEL1TvvcroWzw2Gax4uI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config"
"github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance"
"github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance/common"
)

func getNotebookResourceFunc(cfg *config.Config, state *terraform.ResourceState) (interface{}, error) {
Expand Down Expand Up @@ -95,6 +96,105 @@ resource "huaweicloud_modelarts_notebook" "test" {
`, rName)
}

func TestAccResourceNotebook_dedicated(t *testing.T) {
var instance notebook.CreateOpts
resourceName := "huaweicloud_modelarts_notebook.test"
name := acceptance.RandomAccResourceNameWithDash()

rc := acceptance.InitResourceCheck(
resourceName,
&instance,
getNotebookResourceFunc,
)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acceptance.TestAccPreCheck(t) },
ProviderFactories: acceptance.TestAccProviderFactories,
CheckDestroy: rc.CheckResourceDestroy(),
Steps: []resource.TestStep{
{
Config: testAccNotebook_dedicated(name),
Check: resource.ComposeTestCheckFunc(
rc.CheckResourceExists(),
resource.TestCheckResourceAttr(resourceName, "name", name),
resource.TestCheckResourceAttr(resourceName, "flavor_id", "modelarts.vm.cpu.2u"),
resource.TestCheckResourceAttr(resourceName, "volume.0.type", "EFS"),
resource.TestCheckResourceAttr(resourceName, "volume.0.ownership", "DEDICATED"),
resource.TestCheckResourceAttrPair(resourceName, "volume.0.uri", "huaweicloud_sfs_turbo.test", "export_location"),
resource.TestCheckResourceAttrPair(resourceName, "volume.0.id", "huaweicloud_sfs_turbo.test", "id"),
resource.TestCheckResourceAttr(resourceName, "auto_stop_enabled", "false"),
resource.TestCheckResourceAttrSet(resourceName, "image_name"),
resource.TestCheckResourceAttrSet(resourceName, "image_swr_path"),
resource.TestCheckResourceAttrSet(resourceName, "image_type"),
resource.TestCheckResourceAttrSet(resourceName, "created_at"),
resource.TestCheckResourceAttrSet(resourceName, "updated_at"),
resource.TestCheckResourceAttrSet(resourceName, "url"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccNotebook_dedicated(rName string) string {
return fmt.Sprintf(`
%[1]s
data "huaweicloud_availability_zones" "test" {}
resource "huaweicloud_sfs_turbo" "test" {
name = "%[2]s"
size = 1228
share_proto = "NFS"
share_type = "HPC"
hpc_bandwidth = "40M"
vpc_id = huaweicloud_vpc.test.id
subnet_id = huaweicloud_vpc_subnet.test.id
security_group_id = huaweicloud_networking_secgroup.test.id
availability_zone = data.huaweicloud_availability_zones.test.names[0]
}
resource "huaweicloud_modelarts_network" "test" {
name = "%[2]s"
cidr = "172.16.0.0/12"
peer_connections {
vpc_id = huaweicloud_vpc.test.id
subnet_id = huaweicloud_vpc_subnet.test.id
}
}
resource "huaweicloud_modelarts_resource_pool" "test" {
name = "%[2]s"
scope = ["Notebook", "Train", "Infer"]
network_id = huaweicloud_modelarts_network.test.id
resources {
flavor_id = "modelarts.vm.cpu.8ud"
count = 1
}
}
resource "huaweicloud_modelarts_notebook" "test" {
name = "%[2]s"
flavor_id = "modelarts.vm.cpu.2u"
image_id = "e1a07296-22a8-4f05-8bc8-e936c8e54090"
pool_id = huaweicloud_modelarts_resource_pool.test.id
volume {
type = "EFS"
ownership = "DEDICATED"
uri = huaweicloud_sfs_turbo.test.export_location
id = huaweicloud_sfs_turbo.test.id
}
}
`, common.TestBaseNetwork(rName), rName)
}

func TestAccResourceNotebook_all(t *testing.T) {
var instance notebook.CreateOpts
resourceName := "huaweicloud_modelarts_notebook.test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func testModelartsResourcePool_basic_update(name string) string {
resource "huaweicloud_modelarts_resource_pool" "test" {
name = "%s"
description = "This is a demo update"
scope = ["Train", "Infer"]
scope = ["Infer", "Train"]
network_id = huaweicloud_modelarts_network.test.id
resources {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ func ResourceNotebook() *schema.Resource {
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[A-Za-z][\w]{1,64}$`),
"The name consists of 1 to 64 characters, starting with a letter. "+
"Only letters, digits and underscores (_) are allowed."),
},
"flavor_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -90,6 +87,11 @@ func ResourceNotebook() *schema.Resource {
Optional: true,
ForceNew: true,
},
"id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"mount_path": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -378,9 +380,14 @@ func buildVolumeParamter(d *schema.ResourceData) (*notebook.VolumeReq, error) {
if rst.Category == "EFS" && rst.Ownership == "DEDICATED" {
v, ok := d.GetOk("volume.0.uri")
if !ok {
return nil, fmt.Errorf("uri is mandatory if the storage type is EFS and ownership is DEDICATED")
return nil, fmt.Errorf("the parameter 'uri' is mandatory if the storage type is EFS and ownership is DEDICATED")
}
rst.Uri = v.(string)
v, ok = d.GetOk("volume.0.id")
if !ok {
return nil, fmt.Errorf("the parameter 'id' is mandatory if the storage type is EFS and ownership is DEDICATED")
}
rst.ID = v.(string)
}

if v, ok := d.GetOk("volume.0.size"); ok {
Expand Down Expand Up @@ -408,6 +415,8 @@ func setVolumeToState(d *schema.ResourceData, volume notebook.VolumeRes) error {
result["type"] = volume.Category
result["ownership"] = volume.Ownership
result["size"] = volume.Capacity
result["uri"] = volume.URI
result["id"] = volume.ID
result["mount_path"] = volume.MountPath
return d.Set("volume", []map[string]interface{}{result})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,16 @@ func ResourceModelartsResourcePool() *schema.Resource {
Description: `The name of the resource pool.`,
},
"scope": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Computed: true,
Description: `List of job types supported by the resource pool.`,
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Computed: true,
Description: utils.SchemaDesc(
`List of job types supported by the resource pool.`,
utils.SchemaDescInput{
Required: true,
},
),
},
"resources": {
Type: schema.TypeList,
Expand Down Expand Up @@ -305,6 +310,42 @@ func modelartsResourcePoolUserLoginSchema() *schema.Resource {
return &sc
}

func scopeStatusRefreshFunc(cfg *config.Config, region string, d *schema.ResourceData, scopes []interface{}) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
getResourcePoolRespBody, err := queryResourcePool(cfg, region, d)
if err != nil {
return getResourcePoolRespBody, "ERROR", err
}

for _, scope := range scopes {
scopeStatus := fmt.Sprintf("status.scope[?scopeType=='%s']|[0].state", scope)
if utils.PathSearch(scopeStatus, getResourcePoolRespBody, "").(string) != "Enabled" {
return "No matches found", "PENDING", nil
}
}
return "Matched", "COMPLETED", nil
}
}

func createResourcePoolWaitingForScopesCompleted(ctx context.Context, d *schema.ResourceData, meta interface{}, timeout time.Duration) error {
cfg := meta.(*config.Config)
region := cfg.GetRegion(d)
stateConf := &resource.StateChangeConf{
Pending: []string{"PENDING"},
Target: []string{"COMPLETED"},
Refresh: scopeStatusRefreshFunc(cfg, region, d, d.Get("scope").(*schema.Set).List()),
Timeout: timeout,
// In most cases, the bind operation will be completed immediately, but in a few cases, it needs to wait
// for a short period of time, and the polling is performed by incrementing the time here.
PollInterval: 10 * time.Second,
}
_, err := stateConf.WaitForStateContext(ctx)
if err != nil {
return fmt.Errorf("error waiting for the scope statuses are both completed: %s", err)
}
return nil
}

func resourceModelartsResourcePoolCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
cfg := meta.(*config.Config)
region := cfg.GetRegion(d)
Expand Down Expand Up @@ -376,6 +417,11 @@ func resourceModelartsResourcePoolCreate(ctx context.Context, d *schema.Resource
if err != nil {
return diag.Errorf("error waiting for the Modelarts resource pool (%s) creation to complete: %s", d.Id(), err)
}

err = createResourcePoolWaitingForScopesCompleted(ctx, d, meta, d.Timeout(schema.TimeoutCreate))
if err != nil {
return diag.Errorf("error waiting for the Modelarts resource pool (%s) creation to complete: %s", d.Id(), err)
}
return resourceModelartsResourcePoolRead(ctx, d, meta)
}

Expand Down Expand Up @@ -429,7 +475,7 @@ func buildCreateResourcePoolMetaDataAnnotationsBodyParams(d *schema.ResourceData
func buildCreateResourcePoolSpecBodyParams(d *schema.ResourceData) map[string]interface{} {
params := map[string]interface{}{
"type": "Dedicate",
"scope": utils.ValueIngoreEmpty(d.Get("scope")),
"scope": utils.ValueIngoreEmpty(d.Get("scope").(*schema.Set).List()),
"resources": buildResourcePoolSpecResources(d),
"userLogin": buildCreateResourcePoolSpecUserLoginBodyParams(d),
"network": buildCreateResourcePoolSpecNetworkBodyParams(d),
Expand Down Expand Up @@ -842,7 +888,7 @@ func buildUpdateResourcePoolMetaDataAnnotationsBodyParams(d *schema.ResourceData

func buildUpdateResourcePoolSpecBodyParams(d *schema.ResourceData) map[string]interface{} {
params := map[string]interface{}{
"scope": utils.ValueIngoreEmpty(d.Get("scope")),
"scope": utils.ValueIngoreEmpty(d.Get("scope").(*schema.Set).List()),
"resources": buildResourcePoolSpecResources(d),
}
return params
Expand Down Expand Up @@ -937,8 +983,8 @@ func updateResourcePoolWaitingForStateCompleted(ctx context.Context, d *schema.R
}

// check if the resource pool is in the process of changing scope
if rawArray, ok := d.Get("scope").([]string); ok {
for _, v := range rawArray {
if rawArray, ok := d.GetOk("scope"); ok {
for _, v := range rawArray.(*schema.Set).List() {
scopeStatus := fmt.Sprintf("status.scope[?scopeType=='%s']|[0].state", v)
log.Println("scopeStatus: ", scopeStatus)
if utils.PathSearch(scopeStatus, getResourcePoolRespBody, "").(string) != "Enabled" {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d0a7418

Please sign in to comment.