Skip to content

Commit df571ee

Browse files
committed
Add CnsSharedDiskVolumeClient CRD
1 parent 912cd98 commit df571ee

File tree

8 files changed

+495
-0
lines changed

8 files changed

+495
-0
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cnssharedblockvolumeinfo
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"sync"
23+
24+
"k8s.io/apimachinery/pkg/api/errors"
25+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/types"
27+
"k8s.io/client-go/tools/cache"
28+
"sigs.k8s.io/controller-runtime/pkg/client"
29+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
30+
31+
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger"
32+
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/internalapis/cnsoperator/cnssharedblockvolumeinfo/v1alpha1"
33+
cnsoperatortypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/types"
34+
)
35+
36+
// SharedBlockVolumeInfo exposes an interface to support adding
37+
// and removing information about attached VMs to a PVC.
38+
type SharedBlockVolumeInfo interface {
39+
// Add adds the input VM UUID to the list of
40+
// attached VMs for the given shared block volume.
41+
AddVmToAttachedList(ctx context.Context, sharedBlockVolumeName, VmUUID string) error
42+
// RemoveVmFromAttachedList removes the input VM UUID from
43+
// the list of attached VMs for the given shared block volume.
44+
RemoveVmFromAttachedList(ctx context.Context, sharedBlockVolumeName, VmUUID string) error
45+
}
46+
47+
// sharedBlockVolumeInfo maintains a client to the API
48+
// server for operations on SharedBlockVolumeInfo instance.
49+
// It also contains a per instance lock to handle
50+
// concurrent operations.
51+
type sharedBlockVolumeInfo struct {
52+
client client.Client
53+
// Per volume lock for concurrent access to SharedBlockVolumeInfo instances.
54+
// Keys are strings representing PVC names.
55+
// Values are individual sync.Mutex locks that need to be held
56+
// to make updates to the SharedBlockVolumeInfo instance on the API server.
57+
volumeLock *sync.Map
58+
}
59+
60+
var (
61+
sharedBlockClientInstanceLock sync.Mutex
62+
sharedBlockVolumeClientInstance *sharedBlockVolumeInfo
63+
)
64+
65+
// Add adds the input VM UUID to the list of
66+
// attached VMs for the given shared block volume.
67+
// Callers need to specify sharedBlockVolumeName as a combination of
68+
// "<SV-namespace>/<SV-PVC-name>". This combination is used to uniquely
69+
// identify CnsSharedBlockVolumeInfo instances.
70+
// The instance is created if it doesn't exist.
71+
// Returns an error if the operation cannot be persisted on the API server.
72+
func (f *sharedBlockVolumeInfo) AddVmToAttachedList(ctx context.Context,
73+
sharedBlockVolumeName, VmUUID string) error {
74+
log := logger.GetLogger(ctx)
75+
76+
log.Infof("Adding VM %s to cnsSharedBlockVolumeInfo %s",
77+
VmUUID, sharedBlockVolumeName)
78+
actual, _ := f.volumeLock.LoadOrStore(sharedBlockVolumeName, &sync.Mutex{})
79+
instanceLock, ok := actual.(*sync.Mutex)
80+
if !ok {
81+
return fmt.Errorf("failed to cast lock for cnsSharedBlockVolumeInfo instance: %s", sharedBlockVolumeName)
82+
}
83+
instanceLock.Lock()
84+
defer instanceLock.Unlock()
85+
86+
instance := &v1alpha1.CnsSharedBlockVolumeInfo{}
87+
instanceNamespace, instanceName, err := cache.SplitMetaNamespaceKey(sharedBlockVolumeName)
88+
if err != nil {
89+
log.Errorf("failed to split key %s with error: %+v", sharedBlockVolumeName, err)
90+
return err
91+
}
92+
instanceKey := types.NamespacedName{
93+
Namespace: instanceNamespace,
94+
Name: instanceName,
95+
}
96+
err = f.client.Get(ctx, instanceKey, instance)
97+
if err != nil {
98+
if errors.IsNotFound(err) {
99+
// Create the instance as it does not exist on the API server.
100+
instance = &v1alpha1.CnsSharedBlockVolumeInfo{
101+
ObjectMeta: v1.ObjectMeta{
102+
Name: instanceName,
103+
Namespace: instanceNamespace,
104+
// Add finalizer so that CnSharedBlockVolumeInfo instance doesn't get deleted abruptly
105+
Finalizers: []string{cnsoperatortypes.CNSFinalizer},
106+
},
107+
Spec: v1alpha1.CnsSharedBlockVolumeInfoSpec{
108+
AttachedVms: []string{
109+
VmUUID,
110+
},
111+
},
112+
}
113+
log.Debugf("Creating cnsSharedBlockVolumeInfo instance %s with spec: %+v", sharedBlockVolumeName, instance)
114+
err = f.client.Create(ctx, instance)
115+
if err != nil {
116+
log.Errorf("failed to create cnsSharedBlockVolumeInfo instance %s with error: %+v", sharedBlockVolumeName, err)
117+
return err
118+
}
119+
return nil
120+
}
121+
log.Errorf("failed to get cnsSharedBlockVolumeInfo instance %s with error: %+v", sharedBlockVolumeName, err)
122+
return err
123+
}
124+
125+
// Verify if input VmUUID exists in existing AttachedVMs list.
126+
log.Debugf("Verifying if VM %s exists in current list of attached Vms. Current list: %+v",
127+
VmUUID, instance.Spec.AttachedVms)
128+
oldAttachedVmsList := instance.Spec.AttachedVms
129+
for _, oldAttachedVM := range oldAttachedVmsList {
130+
if oldAttachedVM == VmUUID {
131+
log.Debugf("Found VM %s in list. Returning.", VmUUID)
132+
return nil
133+
}
134+
}
135+
newAttachVmsList := append(oldAttachedVmsList, VmUUID)
136+
instance.Spec.AttachedVms = newAttachVmsList
137+
log.Debugf("Updating cnsSharedBlockVolumeInfo instance %s with spec: %+v", sharedBlockVolumeName, instance)
138+
err = f.client.Update(ctx, instance)
139+
if err != nil {
140+
log.Errorf("failed to update cnsSharedBlockVolumeInfo instance %s/%s with error: %+v", sharedBlockVolumeName, err)
141+
}
142+
return err
143+
}
144+
145+
// RemoveVmFromAttachedList removes the input VM UUID from
146+
// the list of attached VMs for the given shared block volume.
147+
// Callers need to specify sharedBlockVolumeName as a combination of
148+
// "<SV-namespace>/<SV-PVC-name>". This combination is used to uniquely
149+
// identify CnsSharedBlockVolumeInfo instances.
150+
// If the given VM was the last client for this file volume, the instance is
151+
// deleted from the API server.
152+
// Returns an error if the operation cannot be persisted on the API server.
153+
func (f *sharedBlockVolumeInfo) RemoveVmFromAttachedList(ctx context.Context,
154+
sharedBlockVolumeName, VmUUID string) error {
155+
log := logger.GetLogger(ctx)
156+
log.Infof("Removing VmUUID %s from cnsSharedBlockVolumeInfo %s",
157+
VmUUID, sharedBlockVolumeName)
158+
actual, _ := f.volumeLock.LoadOrStore(sharedBlockVolumeName, &sync.Mutex{})
159+
instanceLock, ok := actual.(*sync.Mutex)
160+
if !ok {
161+
return fmt.Errorf("failed to cast lock for cnsSharedBlockVolumeInfo instance: %s", sharedBlockVolumeName)
162+
}
163+
instanceLock.Lock()
164+
defer instanceLock.Unlock()
165+
instance := &v1alpha1.CnsSharedBlockVolumeInfo{}
166+
instanceNamespace, instanceName, err := cache.SplitMetaNamespaceKey(sharedBlockVolumeName)
167+
if err != nil {
168+
log.Errorf("failed to split key %s with error: %+v", sharedBlockVolumeName, err)
169+
return err
170+
}
171+
instanceKey := types.NamespacedName{
172+
Namespace: instanceNamespace,
173+
Name: instanceName,
174+
}
175+
err = f.client.Get(ctx, instanceKey, instance)
176+
if err != nil {
177+
if errors.IsNotFound(err) {
178+
log.Infof("cnsSharedBlockVolumeInfo instance %s does not exist on API server", sharedBlockVolumeName)
179+
return nil
180+
}
181+
log.Errorf("failed to get cnsSharedBlockVolumeInfo instance %s with error: %+v", sharedBlockVolumeName, err)
182+
return err
183+
}
184+
185+
log.Debugf("Verifying if VM UUID %s exists in list of already attached VMs. Current list: %+v",
186+
sharedBlockVolumeName, instance.Spec.AttachedVms)
187+
for index, existingAttachedtVM := range instance.Spec.AttachedVms {
188+
if sharedBlockVolumeName == existingAttachedtVM {
189+
log.Debugf("Removing VmUUID %s from Attached VMs list", VmUUID)
190+
instance.Spec.AttachedVms = append(
191+
instance.Spec.AttachedVms[:index],
192+
instance.Spec.AttachedVms[index+1:]...)
193+
if len(instance.Spec.AttachedVms) == 0 {
194+
log.Infof("Deleting cnsSharedBlockVolumeInfo instance %s from API server", sharedBlockVolumeName)
195+
// Remove finalizer from CnsSharedBlockVolumeInfo instance
196+
err = removeFinalizer(ctx, f.client, instance)
197+
if err != nil {
198+
log.Errorf("failed to remove finalizer from cnsSharedBlockVolumeInfo instance %s with error: %+v",
199+
sharedBlockVolumeName, err)
200+
}
201+
err = f.client.Delete(ctx, instance)
202+
if err != nil {
203+
// In case of namespace deletion, we will have deletion timestamp added on the
204+
// CnsSharedBlockVolumeInfo instance. So, as soon as we delete finalizer, instance might
205+
// get deleted immediately. In such cases we will get NotFound error here, return success
206+
// if instance is already deleted.
207+
if errors.IsNotFound(err) {
208+
log.Infof("cnsSharedBlockVolumeInfo instance %s seems to be already deleted.", sharedBlockVolumeName)
209+
f.volumeLock.Delete(sharedBlockVolumeName)
210+
return nil
211+
}
212+
log.Errorf("failed to delete cnsSharedBlockVolumeInfo instance %s with error: %+v", sharedBlockVolumeName, err)
213+
return err
214+
}
215+
f.volumeLock.Delete(sharedBlockVolumeName)
216+
return nil
217+
}
218+
log.Debugf("Updating cnsSharedBlockVolumeInfo instance %s with spec: %+v", sharedBlockVolumeName, instance)
219+
err = f.client.Update(ctx, instance)
220+
if err != nil {
221+
log.Errorf("failed to update cnsSharedBlockVolumeInfo instance %s with error: %+v", sharedBlockVolumeName, err)
222+
}
223+
return err
224+
}
225+
}
226+
log.Debugf("Could not find VM %s in list. Returning.", VmUUID)
227+
return nil
228+
}
229+
230+
// removeFinalizer will remove the CNS Finalizer = cns.vmware.com,
231+
// from a given CnsSharedBlockVolumeInfo instance.
232+
func removeFinalizer(ctx context.Context, client client.Client,
233+
instance *v1alpha1.CnsSharedBlockVolumeInfo) error {
234+
if !controllerutil.ContainsFinalizer(instance, cnsoperatortypes.CNSFinalizer) {
235+
// Finalizer not present on instance. Nothing to do.
236+
return nil
237+
}
238+
239+
if !controllerutil.AddFinalizer(instance, cnsoperatortypes.CNSFinalizer) {
240+
return fmt.Errorf("failed to add CNS finalizer %s to CnsSharedBlockVolumeInfo "+
241+
"instance %s in namespace %s", cnsoperatortypes.CNSFinalizer, instance.Name,
242+
instance.Namespace)
243+
}
244+
245+
err := client.Update(ctx, instance)
246+
if err != nil {
247+
return fmt.Errorf("failed to update finalizer CnsSharedBlockVolumeInfo instance with name: %q on namespace: %q",
248+
instance.Name, instance.Namespace)
249+
}
250+
251+
return nil
252+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
Copyright 2025 The Kubernetes authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// CnsSharedBlockVolumeInfoSpec contains the list of VM UUIDs attached
24+
// to the given volume
25+
type CnsSharedBlockVolumeInfoSpec struct {
26+
AttachedVms []string `json:"attachedVms,omitempty"`
27+
}
28+
29+
// +kubebuilder:object:root=true
30+
// +kubebuilder:subresource:status
31+
32+
// CnsSharedBlockVolumeInfo is the Schema for the cnssharedblockvolumeinfoes CRD. This CRD is used by
33+
// CNS-CSI for internal bookkeeping purposes only and is not an API.
34+
type CnsSharedBlockVolumeInfo struct {
35+
metav1.TypeMeta `json:",inline"`
36+
metav1.ObjectMeta `json:"metadata,omitempty"`
37+
38+
Spec CnsSharedBlockVolumeInfoSpec `json:"spec,omitempty"`
39+
}
40+
41+
// +kubebuilder:object:root=true
42+
43+
// CnsSharedBlockVolumeInfoList contains a list of CnsSharedBlockVolumeInfo
44+
type CnsSharedBlockVolumeInfoList struct {
45+
metav1.TypeMeta `json:",inline"`
46+
metav1.ListMeta `json:"metadata,omitempty"`
47+
Items []CnsSharedBlockVolumeInfo `json:"items"`
48+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// +k8s:deepcopy-gen=package
2+
// +k8s:defaulter-gen=TypeMeta
3+
// +groupName=cns.vmware.com
4+
5+
package v1alpha1

0 commit comments

Comments
 (0)