Skip to content

Commit c0aa1db

Browse files
committed
Update Cluster resource to add nfs datastore mount and unmount operations
1 parent 75930b2 commit c0aa1db

File tree

9 files changed

+789
-35
lines changed

9 files changed

+789
-35
lines changed

mmv1/products/vmwareengine/Cluster.yaml

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ async:
4545
resource_inside_response: false
4646
include_project: true
4747
custom_code:
48+
encoder: 'templates/terraform/encoders/vmwareengine_cluster.go.tmpl'
49+
pre_update: 'templates/terraform/pre_update/vmwareengine_cluster.go.tmpl'
50+
post_update: 'templates/terraform/post_update/vmwareengine_cluster.go.tmpl'
51+
post_create: 'templates/terraform/post_create/vmwareengine_cluster.go.tmpl'
52+
constants: 'templates/terraform/constants/vmwareengine_cluster.go.tmpl'
4853
# There is a handwritten sweeper that provides a list of locations to sweep
4954
exclude_sweeper: true
5055
error_abort_predicates:
@@ -73,6 +78,37 @@ examples:
7378
region: 'REGION'
7479
# update tests will take care of create and update. PC and cluster creation is expensive and node reservation is required.
7580
exclude_test: true
81+
- name: 'vmware_engine_cluster_nfs_datastore_filestore'
82+
primary_resource_id: 'vmw-ext-cluster'
83+
vars:
84+
name: 'ext-cluster'
85+
private_cloud_id: 'sample-pc'
86+
network_id: 'pc-nw'
87+
management_cluster_id: 'sample-mgmt-cluster'
88+
filestore_instance_id: 'test-fs-filestore'
89+
datastore_id: ext-fs-datastore
90+
svc_ip_cidr: "10.0.0.0/24"
91+
test_env_vars:
92+
region: 'REGION'
93+
zone: 'ZONE'
94+
# Parent PC creation is expensive and requires node reservation. The update test tests all operations.
95+
exclude_test: true
96+
- name: 'vmware_engine_cluster_nfs_datastore_netapp'
97+
primary_resource_id: 'vmw-ext-cluster'
98+
vars:
99+
name: 'ext-cluster'
100+
private_cloud_id: 'sample-pc'
101+
network_id: 'pc-nw'
102+
management_cluster_id: 'sample-mgmt-cluster'
103+
filestore_instance_id: 'test-fs-filestore'
104+
datastore_id: ext-fs-datastore
105+
svc_ip_cidr: "10.0.0.0/24"
106+
test_env_vars:
107+
region: 'REGION'
108+
zone: 'ZONE'
109+
# Parent PC creation is expensive and requires node reservation. The update test tests all operations.
110+
exclude_test: true
111+
76112
parameters:
77113
- name: 'parent'
78114
type: String
@@ -252,3 +288,98 @@ properties:
252288
Minimum cool down period is 30m.
253289
Cool down period must be in whole minutes (for example, 30m, 31m, 50m).
254290
Mandatory for successful addition of autoscaling settings in cluster.
291+
- name: datastoreMountConfig
292+
type: Array
293+
description: |
294+
Optional. Configuration to mount a datastore.
295+
Mount can be done along with cluster create or during cluster update
296+
Since service subnet is not configured with ip range on mgmt cluster creation, mount on management cluster is done as update only
297+
for unmount remove 'datastore_mount_config' config from the update of cluster resource
298+
item_type:
299+
type: NestedObject
300+
properties:
301+
- name: datastore
302+
type: String
303+
description: |-
304+
The resource name of the datastore to unmount.
305+
The datastore requested to be mounted should be in same region/zone as the
306+
cluster.
307+
Resource names are schemeless URIs that follow the conventions in
308+
https://cloud.google.com/apis/design/resource_names.
309+
For example:
310+
`projects/my-project/locations/us-central1/datastores/my-datastore`
311+
required: true
312+
- name: datastoreNetwork
313+
type: NestedObject
314+
description: The network configuration for the datastore.
315+
required: true
316+
properties:
317+
- name: connectionCount
318+
type: Integer
319+
description: |-
320+
Optional. The number of connections of the NFS volume.
321+
Spported from vsphere 8.0u1. Possible values are 1-4.
322+
Default value is 4.
323+
- name: mtu
324+
type: Integer
325+
description: |-
326+
Optional. The Maximal Transmission Unit (MTU) of the datastore.
327+
System sets default MTU size. It prefers the VPC peering MTU, falling
328+
back to the VEN MTU if no peering MTU is found. when detected, and
329+
falling back to the VEN MTU otherwise.
330+
- name: networkPeering
331+
type: String
332+
description: |-
333+
The resource name of the network peering, used to access the
334+
file share by clients on private cloud. Resource names are schemeless
335+
URIs that follow the conventions in
336+
https://cloud.google.com/apis/design/resource_names.
337+
e.g.
338+
projects/my-project/locations/us-central1/networkPeerings/my-network-peering
339+
output: true
340+
- name: subnet
341+
type: String
342+
description: |-
343+
The resource name of the subnet
344+
Resource names are schemeless URIs that follow the conventions in
345+
https://cloud.google.com/apis/design/resource_names.
346+
e.g. projects/my-project/locations/us-central1/subnets/my-subnet
347+
required: true
348+
- name: fileShare
349+
type: String
350+
description: File share name.
351+
output: true
352+
- name: nfsVersion
353+
type: String
354+
description: |-
355+
Optional. The NFS protocol supported by the NFS volume.
356+
Default value used will be NFS_V3
357+
Possible values:
358+
NFS_V3
359+
- name: accessMode
360+
type: String
361+
description: |-
362+
Optional. NFS is accessed by hosts in either read or read_write mode
363+
Optional. Default value used will be READ_WRITE
364+
Possible values:
365+
READ_ONLY
366+
READ_WRITE
367+
- name: servers
368+
type: Array
369+
description: |-
370+
Server IP addresses of the NFS volume.
371+
For NFS 3, you can only provide a single
372+
server IP address or DNS names.
373+
output: true
374+
item_type:
375+
type: String
376+
- name: ignoreColocation
377+
type: Boolean
378+
ignore_read: true
379+
description: |-
380+
Optional. If set to true, the colocation requirement will be ignored.
381+
If set to false, the colocation requirement will be enforced.
382+
If not set, the colocation requirement will be enforced.
383+
Colocation requirement is the requirement that the cluster must be in the
384+
same region/zone of datastore(regional/zonal datastore).
385+
151,1 Bo
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Mount and unmount on the clusters are performed
2+
// on the basis of a new datastore is being added or removed
3+
// This function doesn't account any change in the datastore mount config fields
4+
// Diff is calculated based on different Datastore full reosurce name
5+
func calculateDatastoreMountsDiff(exsitingDatastoreMountCfg, requestedDatastoreMountCfg []interface{}) []interface{} {
6+
existingCfgMap := make(map[string]map[string]interface{})
7+
for _, item := range exsitingDatastoreMountCfg {
8+
m := item.(map[string]interface{})
9+
if datastore, ok := m["datastore"].(string); ok && datastore != "" {
10+
existingCfgMap[datastore] = m
11+
}
12+
}
13+
14+
var diff []interface{}
15+
for _, item := range requestedDatastoreMountCfg {
16+
m := item.(map[string]interface{})
17+
if datastore, ok := m["datastore"].(string); ok && datastore != "" {
18+
if _, exists := existingCfgMap[datastore]; !exists {
19+
diff = append(diff, item)
20+
}
21+
}
22+
}
23+
return diff
24+
}
25+
26+
// For mount and unmount operation, update_mask is not used.
27+
func removeDatastoreMountConfigFieldFromUpdateMask (url string) (string) {
28+
29+
fieldToRemove := "datastoreMountConfig"
30+
encodedComma := "%2C"
31+
32+
// Matches ",field" to remove the field and the preceding comma.
33+
// e.g., foo%2CdatastoreMountConfig -> foo
34+
reCommaField := regexp.MustCompile(encodedComma + fieldToRemove)
35+
36+
// Matches "field," to remove the field and the succeeding comma.
37+
// e.g., datastoreMountConfig%2Cbar -> bar
38+
reFieldComma := regexp.MustCompile(fieldToRemove + encodedComma)
39+
40+
// Matches the field only if it's the entire string.
41+
// e.g., datastoreMountConfig -> ""
42+
reFieldOnly := regexp.MustCompile(fieldToRemove + `(?:%20|\+)*$`)
43+
44+
// Remove instances like "...%2CdatastoreMountConfig"
45+
url = reCommaField.ReplaceAllString(url, "")
46+
47+
// Remove instances like "datastoreMountConfig%2C..."
48+
url = reFieldComma.ReplaceAllString(url, "")
49+
50+
// Remove instance where the mask only contains "datastoreMountConfig"
51+
url = reFieldOnly.ReplaceAllString(url, "")
52+
53+
return url
54+
}
55+
56+
func mountDatastores(mountsToAdd []interface{}, d *schema.ResourceData, config *transport_tpg.Config) error {
57+
var project string
58+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
59+
if err != nil {
60+
return err
61+
}
62+
63+
// Mount operations
64+
for _, mount := range mountsToAdd {
65+
mountMap := mount.(map[string]interface{})
66+
datastore, ok := mountMap["datastore"].(string)
67+
if !ok || datastore == "" {
68+
return fmt.Errorf("no datastore found in the mount request : %#v", mountMap)
69+
}
70+
71+
datastoreMountConfigPayload := map[string]interface{}{
72+
"datastore": datastore,
73+
}
74+
75+
if networkData, ok := mountMap["datastore_network"]; ok {
76+
datastoreMountConfigPayload["datastoreNetwork"] = networkData.([]interface{})[0]
77+
}
78+
if v, ok := mountMap["nfs_version"]; ok && v.(string) != "" {
79+
datastoreMountConfigPayload["nfsVersion"] = v.(string)
80+
}
81+
if v, ok := mountMap["access_mode"]; ok && v.(string) != "" {
82+
datastoreMountConfigPayload["accessMode"] = v.(string)
83+
}
84+
85+
req := map[string]interface{}{
86+
"datastoreMountConfig": datastoreMountConfigPayload,
87+
}
88+
if v, ok := mountMap["ignore_colocation"]; ok {
89+
req["ignoreColocation"] = v.(bool)
90+
}
91+
92+
// Construct the URL directly using d.Id() for the resource name
93+
//mountUrl := fmt.Sprintf("%s%s:mountDatastore", config.VmwareengineBasePath, d.Id())
94+
mountUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}VmwareengineBasePath{{"}}"}}{{"{{"}}parent{{"}}"}}/clusters/{{"{{"}}name{{"}}"}}:mountDatastore")
95+
if err != nil {
96+
return err
97+
}
98+
log.Printf("[DEBUG] MountDatastore request on a Cluster %q: url: %q reqBody: %#v", d.Id(), mountUrl, req)
99+
op, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
100+
Config: config,
101+
Method: "POST",
102+
RawURL: mountUrl,
103+
UserAgent: userAgent,
104+
Body: req,
105+
Timeout: d.Timeout(schema.TimeoutUpdate),
106+
})
107+
if err != nil {
108+
return fmt.Errorf("error calling MountDatastore for %q: %s", datastore, err)
109+
}
110+
111+
err = VmwareengineOperationWaitTime(
112+
config, op, project, "Mount Datastore", userAgent,
113+
d.Timeout(schema.TimeoutUpdate))
114+
if err != nil {
115+
return err
116+
}
117+
}
118+
return nil
119+
}
120+
121+
122+
func unmountDatastores(mountsToRemove []interface{}, d *schema.ResourceData, config *transport_tpg.Config) error {
123+
var project string
124+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
125+
if err != nil {
126+
return err
127+
}
128+
129+
// Unmount operations
130+
for _, mount := range mountsToRemove {
131+
mountMap := mount.(map[string]interface{})
132+
datastore, ok := mountMap["datastore"].(string)
133+
if !ok || datastore == "" {
134+
return fmt.Errorf("no datastore found in the unmount request : %#v", mountMap)
135+
}
136+
137+
//unmountUrl := fmt.Sprintf("%s%s:unmountDatastore", config.VmwareengineBasePath, d.Id())
138+
unmountUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}VmwareengineBasePath{{"}}"}}{{"{{"}}parent{{"}}"}}/clusters/{{"{{"}}name{{"}}"}}:unmountDatastore")
139+
if err != nil {
140+
return err
141+
}
142+
req := map[string]interface{}{
143+
"datastore": datastore,
144+
}
145+
146+
log.Printf("[DEBUG] UnmountDatastore request on a Cluster %q: url: %q reqBody: %#v", d.Id(), unmountUrl, req)
147+
op, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
148+
Config: config,
149+
Method: "POST",
150+
RawURL: unmountUrl,
151+
UserAgent: userAgent,
152+
Body: req,
153+
Timeout: d.Timeout(schema.TimeoutUpdate), // Adjust timeout if necessary
154+
})
155+
if err != nil {
156+
return fmt.Errorf("error calling UnmountDatastore for %q: %s", datastore, err)
157+
}
158+
159+
// Assuming VmwareengineOperationWaitTime is a function to poll the LRO
160+
err = VmwareengineOperationWaitTime(
161+
config, op, project, "Unmount Datastore", userAgent,
162+
d.Timeout(schema.TimeoutUpdate)) // Adjust timeout if necessary
163+
if err != nil {
164+
return err
165+
}
166+
log.Printf("[DEBUG] Successfully unmounted Datastore %q from Cluster %q", datastore, d.Id())
167+
}
168+
return nil
169+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// datastoreMountConfig is sent as post_create and post_update but not sent during create and update methods.
2+
delete(obj, "datastoreMountConfig")
3+
log.Printf("[DEBUG] After removal of datastoreMountConfig Cluster request %q: %#v", d.Id(), obj)
4+
return obj, nil

0 commit comments

Comments
 (0)