diff --git a/nfs_access_policies.go b/nfs_access_policies.go index 447304bef..9377c4890 100644 --- a/nfs_access_policies.go +++ b/nfs_access_policies.go @@ -94,7 +94,6 @@ type NFSSpaceAccessPolicy struct { // NFSSpaceAccessPolicyUpdateOptions contains fields accepted when updating an NFS Space access policy. type NFSSpaceAccessPolicyUpdateOptions struct { - Label *string `json:"label,omitzero"` Enabled *bool `json:"enabled,omitzero"` VPCs *[]NFSSpaceAccessPolicyVPCOptions `json:"vpcs,omitzero"` MTLSCACert *string `json:"mtls_ca_cert,omitzero"` @@ -116,7 +115,6 @@ type NFSFilesystemAccessPolicy struct { // NFSFilesystemAccessPolicyUpdateOptions contains fields accepted when updating an NFS Filesystem access policy. type NFSFilesystemAccessPolicyUpdateOptions struct { - Label *string `json:"label,omitzero"` Enabled *bool `json:"enabled,omitzero"` LinodeIDs *[]int `json:"linode_ids,omitzero"` SquashPolicy *NFSSquashPolicy `json:"squash_policy,omitzero"` diff --git a/nfs_filesystems.go b/nfs_filesystems.go index 3961dbf17..fb5cfe9bc 100644 --- a/nfs_filesystems.go +++ b/nfs_filesystems.go @@ -43,6 +43,7 @@ type NFSFilesystem struct { MountTargetIPs []string `json:"mount_target_ips"` MountTargetFQDN *string `json:"mount_target_fqdn"` MaxCapacityBytes int64 `json:"max_capacity_bytes"` + MaxFileCount int64 `json:"max_file_count"` SnapshotUsageBytes *int64 `json:"snapshot_usage_bytes"` LDAPConfigID *string `json:"ldap_config_id"` SourceSnapshotID *int `json:"source_snapshot_id"` @@ -56,7 +57,8 @@ type NFSFilesystem struct { type NFSFilesystemCreateOptions struct { Label string `json:"label"` Region string `json:"region"` - MaxCapacityBytes int64 `json:"max_capacity_bytes"` + MaxCapacityBytes *int64 `json:"max_capacity_bytes,omitzero"` + MaxFileCount *int64 `json:"max_file_count,omitzero"` ProtocolVersions *[]NFSProtocolVersion `json:"protocol_versions,omitzero"` Tags *[]string `json:"tags,omitzero"` } @@ -65,6 +67,7 @@ type NFSFilesystemCreateOptions struct { type NFSFilesystemUpdateOptions struct { Label *string `json:"label,omitzero"` MaxCapacityBytes *int64 `json:"max_capacity_bytes,omitzero"` + MaxFileCount *int64 `json:"max_file_count,omitzero"` Tags *[]string `json:"tags,omitzero"` } @@ -124,7 +127,8 @@ func (n NFSFilesystem) GetCreateOptions() NFSFilesystemCreateOptions { result := NFSFilesystemCreateOptions{ Label: n.Label, Region: n.Region, - MaxCapacityBytes: n.MaxCapacityBytes, + MaxCapacityBytes: Pointer(n.MaxCapacityBytes), + MaxFileCount: Pointer(n.MaxFileCount), } if n.ProtocolVersions != nil { @@ -143,6 +147,7 @@ func (n NFSFilesystem) GetUpdateOptions() NFSFilesystemUpdateOptions { result := NFSFilesystemUpdateOptions{ Label: Pointer(n.Label), MaxCapacityBytes: Pointer(n.MaxCapacityBytes), + MaxFileCount: Pointer(n.MaxFileCount), } if n.Tags != nil { diff --git a/regions.go b/regions.go index 7a4055260..ccf995dc1 100644 --- a/regions.go +++ b/regions.go @@ -43,6 +43,7 @@ const ( CapabilityMachineImages RegionCapability = "Machine Images" CapabilityMaintenancePolicy RegionCapability = "Maintenance Policy" CapabilityMetadata RegionCapability = "Metadata" + CapabilityNFSStorage RegionCapability = "NFS Storage" CapabilityNLB RegionCapability = "Network LoadBalancer" CapabilityNodeBalancers RegionCapability = "NodeBalancers" CapabilityObjectStorage RegionCapability = "Object Storage" diff --git a/test/integration/nfs_filesystems_test.go b/test/integration/nfs_filesystems_test.go new file mode 100644 index 000000000..cd1c6130d --- /dev/null +++ b/test/integration/nfs_filesystems_test.go @@ -0,0 +1,360 @@ +package integration + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/linode/linodego/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setNFSFilesystemCreateOptions(t *testing.T, client *linodego.Client) linodego.NFSFilesystemCreateOptions { + return linodego.NFSFilesystemCreateOptions{ + Label: "go-test-nfs-filesystem-" + randLabel(), + Region: getRegionsWithCaps(t, client, []linodego.RegionCapability{linodego.CapabilityNFSStorage})[0], + MaxCapacityBytes: linodego.Pointer(int64(1099511627776)), + MaxFileCount: linodego.Pointer(int64(1500)), + ProtocolVersions: linodego.Pointer([]linodego.NFSProtocolVersion{linodego.NFSProtocolVersionV4}), + Tags: linodego.Pointer([]string{"testing"}), + } +} + +func waitForNFSFilesystemDeleted( + ctx context.Context, + client *linodego.Client, + spaceID int, +) error { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + filesystems, err := client.ListNFSFilesystems(ctx, spaceID, nil) + + if err == nil && len(filesystems) != 0 { + continue + } + + // if API returns [404] Not found, deletion is done + if linodego.IsNotFound(err) { + return nil + } + + return err + } + } +} + +func setupNFSFilesystem( + t *testing.T, + fixtureYaml string, + requireActive bool, + modifiers ...func(opts *linodego.NFSFilesystemCreateOptions), +) (*linodego.Client, *linodego.NFSSpace, *linodego.NFSFilesystem, linodego.NFSFilesystemCreateOptions) { + t.Helper() + client, fixtureTeardown := createTestClient(t, fixtureYaml) + createOpts := setNFSFilesystemCreateOptions(t, client) + + for _, modifier := range modifiers { + modifier(&createOpts) + } + + spaceCreateOpts := getNFSSpaceCreateOptions() + space, err := client.CreateNFSSpace(context.Background(), spaceCreateOpts) + require.NoErrorf(t, err, "Error creating NFS Space: %v", err) + + filesystem, err := client.CreateNFSFilesystem(context.Background(), space.ID, createOpts) + require.NoErrorf(t, err, "Error creating NFS Filesystem: %v", err) + + // Wait for NFS Filesystem status to be 'active' to read the details (if needed) + if requireActive { + ctx := waitContext(t, 240*time.Second) + _, err = client.WaitForNFSFilesystemStatus( + ctx, + space.ID, + filesystem.ID, + linodego.NFSFilesystemStatusActive, + ) + require.NoErrorf(t, err, "Failed to wait for Filesystem status to be active: %s", err) + } + + t.Cleanup(func() { + ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + + if err = client.DeleteNFSFilesystem(context.Background(), space.ID, filesystem.ID); err != nil { + t.Errorf("Error deleting NFS Filesystem: %v", err) + } + + err = waitForNFSFilesystemDeleted(ctx, client, space.ID) + require.NoErrorf(t, err, "Error waiting for NFS Filesystem to be deleted: %v", err) + + if err = client.DeleteNFSSpace(context.Background(), space.ID); err != nil { + t.Errorf("Error deleting NFS Space: %v", err) + } + + fixtureTeardown() + }) + + return client, space, filesystem, createOpts +} + +func setupNFSFilesystemCustomQuota( + t *testing.T, + client *linodego.Client, + space *linodego.NFSSpace, + filesystem *linodego.NFSFilesystem, + modifiers ...func(opts *linodego.NFSQuotaCreateOptions), +) (*linodego.NFSQuota, linodego.NFSQuotaCreateOptions) { + t.Helper() + + _, mountDir, isFound := strings.Cut(*filesystem.MountTargetFQDN, ":/") + require.True(t, isFound, "Mount Target FQDN does not contain ':/'") + + createOpts := linodego.NFSQuotaCreateOptions{ + Path: "/" + strings.Join([]string{mountDir, "testing-dx"}, "/"), + MaxFileCount: filesystem.MaxFileCount, + MaxCapacityBytes: filesystem.MaxCapacityBytes, + } + + for _, modifier := range modifiers { + modifier(&createOpts) + } + + quota, err := client.CreateNFSQuota(context.Background(), space.ID, filesystem.ID, createOpts) + require.NoErrorf(t, err, "Error creating Filesystem Quota: %v", err) + + t.Cleanup(func() { + if err = client.DeleteNFSQuota(context.Background(), space.ID, filesystem.ID, quota.ID); err != nil { + t.Errorf("Error deleting NFS Filesystem Quota: %v", err) + } + }) + + return quota, createOpts +} + +func verifyNFSFilesystemBasics(t *testing.T, space *linodego.NFSSpace, filesystem *linodego.NFSFilesystem, createOpts linodego.NFSFilesystemCreateOptions) { + t.Helper() + assert.Equal(t, space.ID, filesystem.SpaceID) + assert.Equal(t, createOpts.Label, filesystem.Label) + assert.Equal(t, createOpts.Region, filesystem.Region) + assert.Equal(t, createOpts.Tags, linodego.Pointer(filesystem.Tags)) + assert.NotNil(t, filesystem.Status) + assertDateSet(t, filesystem.Created) + assertDateSet(t, filesystem.Updated) +} + +func verifyNFSFilesystemDetails(t *testing.T, filesystem *linodego.NFSFilesystem, createOpts linodego.NFSFilesystemCreateOptions) { + t.Helper() + assert.NotEmpty(t, filesystem.ProtocolVersions) + assert.EqualValues(t, createOpts.MaxCapacityBytes, linodego.Pointer(filesystem.MaxCapacityBytes)) + assert.EqualValues(t, createOpts.MaxFileCount, linodego.Pointer(filesystem.MaxFileCount)) + assert.NotEmpty(t, filesystem.MountTargetIPs) + assert.NotNil(t, filesystem.MountTargetFQDN) + assert.NotNil(t, filesystem.Stats) + // TODO: Specified in API docs, but not available in API itself yet + // assert.NotNil(t, filesystem.SnapshotUsageBytes) + // assert.NotNil(t, filesystem.LDAPConfigID) + // assert.NotNil(t, filesystem.SourceSnapshotID) +} + +func verifyNFSFilesystemAccessPolicyUpdate( + t *testing.T, + filesystemAccPolicy *linodego.NFSFilesystemAccessPolicy, + opts linodego.NFSFilesystemAccessPolicyUpdateOptions, +) { + assert.True(t, filesystemAccPolicy.Enabled) + assert.Len(t, filesystemAccPolicy.LinodeACL, 1) + assert.Equal(t, (*opts.LinodeIDs)[0], filesystemAccPolicy.LinodeACL[0].ID) + assert.Equal(t, opts.SquashPolicy, linodego.Pointer(filesystemAccPolicy.SquashPolicy)) + assertDateSet(t, filesystemAccPolicy.Updated) +} + +func verifyNFSFilesystemQuota( + t *testing.T, + quota *linodego.NFSQuota, + filesystem *linodego.NFSFilesystem, +) { + assert.Equal(t, filesystem.ID, quota.FilesystemID) + assert.Equal(t, filesystem.MaxFileCount, *quota.MaxFileCount) + assert.Equal(t, filesystem.MaxCapacityBytes, *quota.MaxCapacityBytes) + assert.Equal(t, linodego.NFSQuotaStatusActive, quota.Status) + assertDateSet(t, quota.Created) + assertDateSet(t, quota.Updated) +} + +func TestNFSFilesystem_Create_smoke(t *testing.T) { + _, space, filesystem, createOpts := setupNFSFilesystem(t, "fixtures/TestNFSFilesystem_Create", false) + verifyNFSFilesystemBasics(t, space, filesystem, createOpts) +} + +func TestNFSFilesystem_Get(t *testing.T) { + client, space, filesystem, createOpts := setupNFSFilesystem(t, "fixtures/TestNFSFilesystem_Get", true) + + filesystemGet, err := client.GetNFSFilesystemInSpace(context.Background(), space.ID, filesystem.ID) + require.NoErrorf(t, err, "Error retrieving NFS Filesystem in Space: %v", err) + verifyNFSFilesystemBasics(t, space, filesystemGet, createOpts) + verifyNFSFilesystemDetails(t, filesystemGet, createOpts) +} + +func TestNFSFilesystem_GetWithoutNFSSpace(t *testing.T) { + client, space, filesystem, createOpts := setupNFSFilesystem(t, "fixtures/TestNFSFilesystem_GetWithoutNFSSpace", false) + + filesystemGet, err := client.GetNFSFilesystem(context.Background(), filesystem.ID) + require.NoErrorf(t, err, "Error retrieving NFS Filesystem with no Space: %v", err) + verifyNFSFilesystemBasics(t, space, filesystemGet, createOpts) +} + +func TestNFSFilesystem_List(t *testing.T) { + client, space, filesystem, createOpts := setupNFSFilesystem(t, "fixtures/TestNFSFilesystem_List", false) + + f := linodego.Filter{} + f.AddField(linodego.Eq, "label", filesystem.Label) + filter, err := f.MarshalJSON() + if err != nil { + t.Fatalf("Failed to marshal filter: %v", err) + } + + filesystemList, err := client.ListNFSFilesystems(context.Background(), space.ID, &linodego.ListOptions{Filter: string(filter)}) + require.NoErrorf(t, err, "Error listing NFS Spaces: %v", err) + assert.Len(t, filesystemList, 1) + verifyNFSFilesystemBasics(t, space, linodego.Pointer(filesystemList[0]), createOpts) +} + +func TestNFSFilesystem_Update(t *testing.T) { + client, space, filesystem, createOpts := setupNFSFilesystem(t, "fixtures/TestNFSFilesystem_Update", true) + + updateOpts := filesystem.GetUpdateOptions() + updateOpts.Label = linodego.Pointer(filesystem.Label + "-updated") + updateOpts.Tags = linodego.Pointer([]string{"updated"}) + updateOpts.MaxCapacityBytes = linodego.Pointer(int64(1099511628000)) + updateOpts.MaxFileCount = linodego.Pointer(int64(3210)) + + filesystemUpdate, err := client.UpdateNFSFilesystem(context.Background(), space.ID, filesystem.ID, updateOpts) + require.NoErrorf(t, err, "Error updating NFS Space: %v", err) + + verifyOpts := linodego.NFSFilesystemCreateOptions{ + Label: *updateOpts.Label, + Region: createOpts.Region, + Tags: updateOpts.Tags, + MaxCapacityBytes: updateOpts.MaxCapacityBytes, + MaxFileCount: updateOpts.MaxFileCount, + } + verifyNFSFilesystemBasics(t, space, filesystemUpdate, verifyOpts) + verifyNFSFilesystemDetails(t, filesystemUpdate, verifyOpts) +} + +func TestNFSFilesystem_GetAccessPolicy_smoke(t *testing.T) { + client, space, filesystem, _ := setupNFSFilesystem(t, "fixtures/TestNFSFilesystem_GetAccessPolicy", false) + + filesystemAccPolicy, err := client.GetNFSFilesystemAccessPolicy(context.Background(), space.ID, filesystem.ID) + require.NoErrorf(t, err, "Error retrieving NFS Filesystem Access Policy: %v", err) + assert.Equal(t, filesystem.ID, filesystemAccPolicy.FilesystemID) + assert.Equal(t, filesystem.Label, filesystemAccPolicy.Label) + assert.False(t, filesystemAccPolicy.Enabled) + // assert.False(t, filesystemAccPolicy.Inherit) // TODO: No Inherit field in API spec, but returned by API response + assert.NotEmpty(t, filesystemAccPolicy.Protocols) + assert.Empty(t, filesystemAccPolicy.LinodeACL) + assert.NotNil(t, filesystemAccPolicy.SquashPolicy) + assert.Equal(t, linodego.NFSAccessPolicyStatusActive, filesystemAccPolicy.Status) + assertDateSet(t, filesystemAccPolicy.Created) + assertDateSet(t, filesystemAccPolicy.Updated) +} + +func TestNFSFilesystem_UpdateAccessPolicy(t *testing.T) { + client, space, filesystem, _ := setupNFSFilesystem(t, "fixtures/TestNFSFilesystem_UpdateAccessPolicy", false) + + linode, err := createInstance( + t, + client, + true, + func(client *linodego.Client, opts *linodego.InstanceCreateOptions) { + opts.Region = filesystem.Region + opts.Booted = linodego.Pointer(true) + }, + ) + require.NoErrorf(t, err, "Error creating Linode for NFS Filesystem Access Policy: %v", err) + + t.Cleanup(func() { + if err := client.DeleteInstance(context.Background(), linode.ID); err != nil { + if t != nil { + t.Errorf("Error deleting Linode instance: %s", err) + } + } + }) + + updateOpts := linodego.NFSFilesystemAccessPolicyUpdateOptions{ + Enabled: linodego.Pointer(true), + LinodeIDs: linodego.Pointer([]int{linode.ID}), + SquashPolicy: linodego.Pointer(linodego.NFSSquashPolicyAllSquash), + } + + filesystemAccPolicyUpdate, err := client.UpdateNFSFilesystemAccessPolicy( + context.Background(), + space.ID, + filesystem.ID, + updateOpts, + ) + require.NoErrorf(t, err, "Error updating NFS Filesystem Access Policy: %v", err) + verifyNFSFilesystemAccessPolicyUpdate(t, filesystemAccPolicyUpdate, updateOpts) + + // Wait for NFS Filesystem Access Policy status to be 'active' to read the details + ctx := waitContext(t, 180*time.Second) + _, err = client.WaitForNFSFilesystemAccessPolicyStatus( + ctx, + space.ID, + filesystem.ID, + linodego.NFSAccessPolicyStatusActive, + ) + require.NoErrorf(t, err, "Failed to wait for Filesystem Access Policy status: %s", err) + + filesystemAccPolicyGet, err := client.GetNFSFilesystemAccessPolicy(context.Background(), space.ID, filesystem.ID) + require.NoErrorf(t, err, "Error getting NFS Filesystem Access Policy: %v", err) + verifyNFSFilesystemAccessPolicyUpdate(t, filesystemAccPolicyGet, updateOpts) +} + +func TestNFSFilesystem_GetRootQuota_smoke(t *testing.T) { + client, space, filesystem, _ := setupNFSFilesystem(t, "fixtures/TestNFSFilesystem_GetRootQuota_smoke", true) + + filesystem, err := client.GetNFSFilesystemInSpace(context.Background(), space.ID, filesystem.ID) + require.NoErrorf(t, err, "Error retrieving NFS Filesystem in Space: %v", err) + + _, mountDir, isFound := strings.Cut(*filesystem.MountTargetFQDN, ":/") + require.True(t, isFound, "Mount Target FQDN does not contain ':/'") + + quotas, err := client.ListNFSQuotas(context.Background(), space.ID, filesystem.ID, nil) + require.NoErrorf(t, err, "Error listing Filesystem Quotas: %v", err) + assert.Len(t, quotas, 1) + assert.Contains(t, quotas[0].Path, mountDir) + assert.True(t, quotas[0].IsRoot) + verifyNFSFilesystemQuota(t, linodego.Pointer(quotas[0]), filesystem) +} + +func TestNFSFilesystem_ManageCustomQuota(t *testing.T) { + t.Skip("Removal of Filesystem with custom Quota requires manual action on mounted Linode instance") + client, space, filesystem, _ := setupNFSFilesystem(t, "fixtures/TestNFSFilesystem_ManageCustomQuota", true) + + filesystem, err := client.GetNFSFilesystemInSpace(context.Background(), space.ID, filesystem.ID) + require.NoErrorf(t, err, "Error retrieving NFS Filesystem in Space: %v", err) + + quota, createOpts := setupNFSFilesystemCustomQuota(t, client, space, filesystem) + assert.Equal(t, createOpts.Path, quota.Path) + assert.False(t, quota.IsRoot) + verifyNFSFilesystemQuota(t, quota, filesystem) + + quota, err = client.GetNFSQuota(context.Background(), space.ID, filesystem.ID, quota.ID) + require.NoErrorf(t, err, "Error retrieving Filesystem Quota: %v", err) + assert.Equal(t, createOpts.Path, quota.Path) + assert.False(t, quota.IsRoot) + verifyNFSFilesystemQuota(t, quota, filesystem) + + quotas, err := client.ListNFSQuotas(context.Background(), space.ID, filesystem.ID, nil) + require.NoErrorf(t, err, "Error listing Filesystem Quotas: %v", err) + assert.Len(t, quotas, 2) +} diff --git a/test/integration/nfs_spaces_test.go b/test/integration/nfs_spaces_test.go new file mode 100644 index 000000000..fa1e22eac --- /dev/null +++ b/test/integration/nfs_spaces_test.go @@ -0,0 +1,169 @@ +package integration + +import ( + "context" + "testing" + "time" + + "github.com/linode/linodego/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func getNFSSpaceCreateOptions() linodego.NFSSpaceCreateOptions { + return linodego.NFSSpaceCreateOptions{ + Label: "go-test-nfs-space-" + randLabel(), + Description: linodego.Pointer("Test description"), + Tags: linodego.Pointer([]string{"testing"}), + } +} + +func setupNFSSpace( + t *testing.T, + fixtureYaml string, + modifiers ...func(opts *linodego.NFSSpaceCreateOptions), +) (*linodego.Client, *linodego.NFSSpace, linodego.NFSSpaceCreateOptions) { + t.Helper() + client, fixtureTeardown := createTestClient(t, fixtureYaml) + createOpts := getNFSSpaceCreateOptions() + + for _, modifier := range modifiers { + modifier(&createOpts) + } + + space, err := client.CreateNFSSpace(context.Background(), createOpts) + require.NoErrorf(t, err, "Error creating NFS Space: %v", err) + + t.Cleanup(func() { + if err = client.DeleteNFSSpace(context.Background(), space.ID); err != nil { + t.Errorf("Error deleting NFS Space: %v", err) + } + fixtureTeardown() + }) + + return client, space, createOpts +} + +func verifyNFSSpace(t *testing.T, space *linodego.NFSSpace, createOpts linodego.NFSSpaceCreateOptions) { + t.Helper() + assert.Equal(t, createOpts.Label, space.Label) + assert.Equal(t, createOpts.Description, space.Description) + assert.Equal(t, createOpts.Tags, linodego.Pointer(space.Tags)) + assert.NotNil(t, space.Status) + assertDateSet(t, space.Created) + assertDateSet(t, space.Updated) +} + +func verifyNFSSpaceAccessPolicyUpdate(t *testing.T, spaceAccPolicy *linodego.NFSSpaceAccessPolicy, opts linodego.NFSSpaceAccessPolicyUpdateOptions) { + assert.True(t, spaceAccPolicy.Enabled) + assert.Len(t, spaceAccPolicy.VPCACL, 1) + assert.Equal(t, (*opts.VPCs)[0].ID, spaceAccPolicy.VPCACL[0].ID) + assert.Nil(t, spaceAccPolicy.MTLSCACert) + assert.Equal(t, linodego.NFSMTLSModeOptional, spaceAccPolicy.MTLSMode) + assertDateSet(t, spaceAccPolicy.Updated) +} + +func TestNFSSpace_Create_smoke(t *testing.T) { + _, space, createOpts := setupNFSSpace(t, "fixtures/TestNFSSpace_Create") + verifyNFSSpace(t, space, createOpts) +} + +func TestNFSSpace_Get(t *testing.T) { + client, space, createOpts := setupNFSSpace(t, "fixtures/TestNFSSpace_Get") + + spaceGet, err := client.GetNFSSpace(context.Background(), space.ID) + require.NoErrorf(t, err, "Error retrieving NFS Space: %v", err) + verifyNFSSpace(t, spaceGet, createOpts) +} + +func TestNFSSpace_List(t *testing.T) { + client, space, createOpts := setupNFSSpace(t, "fixtures/TestNFSSpace_List") + + f := linodego.Filter{} + f.AddField(linodego.Eq, "label", space.Label) + filter, err := f.MarshalJSON() + if err != nil { + t.Fatalf("Failed to marshal filter: %v", err) + } + + spaceList, err := client.ListNFSSpaces(context.Background(), &linodego.ListOptions{Filter: string(filter)}) + require.NoErrorf(t, err, "Error listing NFS Spaces: %v", err) + assert.Len(t, spaceList, 1) + verifyNFSSpace(t, linodego.Pointer(spaceList[0]), createOpts) +} + +func TestNFSSpace_Update(t *testing.T) { + client, space, _ := setupNFSSpace(t, "fixtures/TestNFSSpace_Update") + + updateOpts := space.GetUpdateOptions() + updateOpts.Label = linodego.Pointer(space.Label + "-updated") + updateOpts.Description = linodego.Pointer("Description updated") + updateOpts.Tags = linodego.Pointer([]string{"updated"}) + + spaceUpdate, err := client.UpdateNFSSpace(context.Background(), space.ID, updateOpts) + require.NoErrorf(t, err, "Error updating NFS Space: %v", err) + assert.Equal(t, updateOpts.Label, linodego.Pointer(spaceUpdate.Label)) + assert.Equal(t, updateOpts.Description, spaceUpdate.Description) + assert.Equal(t, updateOpts.Tags, linodego.Pointer(spaceUpdate.Tags)) +} + +func TestNFSSpace_GetAccessPolicy_smoke(t *testing.T) { + client, space, _ := setupNFSSpace(t, "fixtures/TestNFSSpace_GetAccessPolicy") + + spaceAccPolicy, err := client.GetNFSSpaceAccessPolicy(context.Background(), space.ID) + require.NoErrorf(t, err, "Error getting NFS Space Access Policy: %v", err) + assert.Equal(t, space.ID, spaceAccPolicy.SpaceID) + assert.Equal(t, space.Label, spaceAccPolicy.Label) + assert.Equal(t, linodego.NFSAccessPolicyStatusActive, spaceAccPolicy.Status) + assertDateSet(t, spaceAccPolicy.Created) + assertDateSet(t, spaceAccPolicy.Created) +} + +func TestNFSSpace_UpdateAccessPolicy(t *testing.T) { + t.Skip("Access Policy update is stuck with 'updating'/'pending' status") + client, space, _ := setupNFSSpace(t, "fixtures/TestNFSSpace_UpdateAccessPolicy") + + vpc, _, vpcWithSubnetTeardown, err := createVPCWithDualStackSubnet( + t, + client, + func(client *linodego.Client, opts *linodego.VPCCreateOptions) { + opts.Region = getRegionsWithCaps(t, client, []linodego.RegionCapability{ + linodego.CapabilityVPCs, + linodego.CapabilityVPCDualStack, + linodego.CapabilityNFSStorage, + })[0] + }, + ) + require.NoErrorf(t, err, "Error creating Dual Stack VPC for Access Policy: %v", err) + t.Cleanup(vpcWithSubnetTeardown) + + updateOpts := linodego.NFSSpaceAccessPolicyUpdateOptions{ + Enabled: linodego.Pointer(true), + VPCs: linodego.Pointer([]linodego.NFSSpaceAccessPolicyVPCOptions{ + { + ID: vpc.ID, + }, + }), + // MTLSCACert: linodego.Pointer("Test CA Certificate"), + MTLSMode: linodego.Pointer(linodego.NFSMTLSModeOptional), + } + + spaceAccPolicyUpdate, err := client.UpdateNFSSpaceAccessPolicy(context.Background(), space.ID, updateOpts) + require.NoErrorf(t, err, "Error updating NFS Space Access Policy: %v", err) + assert.Equal(t, "pending", string(spaceAccPolicyUpdate.Status)) + verifyNFSSpaceAccessPolicyUpdate(t, spaceAccPolicyUpdate, updateOpts) + + // Wait for NFS Space Access Policy status to be 'active' to read the details + ctx := waitContext(t, 180*time.Second) + _, err = client.WaitForNFSSpaceAccessPolicyStatus( + ctx, + space.ID, + linodego.NFSAccessPolicyStatusActive, + ) + require.NoErrorf(t, err, "Failed to wait for Space Access Policy status: %s", err) + + spaceAccPolicyGet, err := client.GetNFSSpaceAccessPolicy(context.Background(), space.ID) + require.NoErrorf(t, err, "Error getting NFS Space Access Policy: %v", err) + assert.Equal(t, "active", string(spaceAccPolicyGet.Status)) + verifyNFSSpaceAccessPolicyUpdate(t, spaceAccPolicyGet, updateOpts) +}