k8s.io/kubernetes@v1.29.3/test/e2e/storage/drivers/csi-test/mock/service/service.go (about) 1 /* 2 Copyright 2018 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 service 18 19 import ( 20 "fmt" 21 "os" 22 "strings" 23 "sync" 24 "sync/atomic" 25 26 "github.com/container-storage-interface/spec/lib/go/csi" 27 "golang.org/x/net/context" 28 "google.golang.org/grpc/codes" 29 "k8s.io/kubernetes/test/e2e/storage/drivers/csi-test/mock/cache" 30 31 "google.golang.org/protobuf/types/known/timestamppb" 32 ) 33 34 const ( 35 // Name is the name of the CSI plug-in. 36 Name = "io.kubernetes.storage.mock" 37 38 // VendorVersion is the version returned by GetPluginInfo. 39 VendorVersion = "0.3.0" 40 41 // TopologyKey simulates a per-node topology. 42 TopologyKey = Name + "/node" 43 44 // TopologyValue is the one, fixed node on which the driver runs. 45 TopologyValue = "some-mock-node" 46 ) 47 48 // Manifest is the SP's manifest. 49 var Manifest = map[string]string{ 50 "url": "https://github.com/kubernetes/kubernetes/tree/master/test/e2e/storage/drivers/csi-test/mock", 51 } 52 53 type Config struct { 54 DisableAttach bool 55 DriverName string 56 AttachLimit int64 57 NodeExpansionRequired bool 58 VolumeMountGroupRequired bool 59 DisableControllerExpansion bool 60 DisableOnlineExpansion bool 61 PermissiveTargetPath bool 62 EnableTopology bool 63 IO DirIO 64 } 65 66 // DirIO is an abstraction over direct os calls. 67 type DirIO interface { 68 // DirExists returns false if the path doesn't exist, true if it exists and is a directory, an error otherwise. 69 DirExists(path string) (bool, error) 70 // Mkdir creates the directory, but not its parents, with 0755 permissions. 71 Mkdir(path string) error 72 // RemoveAll removes the path and everything contained inside it. It's not an error if the path does not exist. 73 RemoveAll(path string) error 74 // Rename changes the name of a file or directory. The parent directory 75 // of newPath must exist. 76 Rename(oldPath, newPath string) error 77 } 78 79 type OSDirIO struct{} 80 81 func (o OSDirIO) DirExists(path string) (bool, error) { 82 info, err := os.Stat(path) 83 switch { 84 case err == nil && !info.IsDir(): 85 return false, fmt.Errorf("%s: not a directory", path) 86 case err == nil: 87 return true, nil 88 case os.IsNotExist(err): 89 return false, nil 90 default: 91 return false, err 92 } 93 } 94 95 func (o OSDirIO) Mkdir(path string) error { 96 return os.Mkdir(path, os.FileMode(0755)) 97 } 98 99 func (o OSDirIO) RemoveAll(path string) error { 100 return os.RemoveAll(path) 101 } 102 103 func (o OSDirIO) Rename(oldPath, newPath string) error { 104 return os.Rename(oldPath, newPath) 105 } 106 107 // Service is the CSI Mock service provider. 108 type Service interface { 109 csi.ControllerServer 110 csi.IdentityServer 111 csi.NodeServer 112 } 113 114 type service struct { 115 sync.Mutex 116 nodeID string 117 vols []csi.Volume 118 volsRWL sync.RWMutex 119 volsNID uint64 120 snapshots cache.SnapshotCache 121 snapshotsNID uint64 122 config Config 123 } 124 125 type Volume struct { 126 VolumeCSI csi.Volume 127 NodeID string 128 ISStaged bool 129 ISPublished bool 130 ISEphemeral bool 131 ISControllerPublished bool 132 StageTargetPath string 133 TargetPath string 134 } 135 136 var MockVolumes map[string]Volume 137 138 // New returns a new Service. 139 func New(config Config) Service { 140 s := &service{ 141 nodeID: config.DriverName, 142 config: config, 143 } 144 if s.config.IO == nil { 145 s.config.IO = OSDirIO{} 146 } 147 s.snapshots = cache.NewSnapshotCache() 148 s.vols = []csi.Volume{ 149 s.newVolume("Mock Volume 1", gib100), 150 s.newVolume("Mock Volume 2", gib100), 151 s.newVolume("Mock Volume 3", gib100), 152 } 153 MockVolumes = map[string]Volume{} 154 155 s.snapshots.Add(s.newSnapshot("Mock Snapshot 1", "1", map[string]string{"Description": "snapshot 1"})) 156 s.snapshots.Add(s.newSnapshot("Mock Snapshot 2", "2", map[string]string{"Description": "snapshot 2"})) 157 s.snapshots.Add(s.newSnapshot("Mock Snapshot 3", "3", map[string]string{"Description": "snapshot 3"})) 158 159 return s 160 } 161 162 const ( 163 kib int64 = 1024 164 mib int64 = kib * 1024 165 gib int64 = mib * 1024 166 gib100 int64 = gib * 100 167 tib int64 = gib * 1024 168 ) 169 170 func (s *service) newVolume(name string, capcity int64) csi.Volume { 171 vol := csi.Volume{ 172 VolumeId: fmt.Sprintf("%d", atomic.AddUint64(&s.volsNID, 1)), 173 VolumeContext: map[string]string{"name": name}, 174 CapacityBytes: capcity, 175 } 176 s.setTopology(&vol) 177 return vol 178 } 179 180 func (s *service) newVolumeFromSnapshot(name string, capacity int64, snapshotID int) csi.Volume { 181 vol := s.newVolume(name, capacity) 182 vol.ContentSource = &csi.VolumeContentSource{ 183 Type: &csi.VolumeContentSource_Snapshot{ 184 Snapshot: &csi.VolumeContentSource_SnapshotSource{ 185 SnapshotId: fmt.Sprintf("%d", snapshotID), 186 }, 187 }, 188 } 189 s.setTopology(&vol) 190 return vol 191 } 192 193 func (s *service) newVolumeFromVolume(name string, capacity int64, volumeID int) csi.Volume { 194 vol := s.newVolume(name, capacity) 195 vol.ContentSource = &csi.VolumeContentSource{ 196 Type: &csi.VolumeContentSource_Volume{ 197 Volume: &csi.VolumeContentSource_VolumeSource{ 198 VolumeId: fmt.Sprintf("%d", volumeID), 199 }, 200 }, 201 } 202 s.setTopology(&vol) 203 return vol 204 } 205 206 func (s *service) setTopology(vol *csi.Volume) { 207 if s.config.EnableTopology { 208 vol.AccessibleTopology = []*csi.Topology{ 209 { 210 Segments: map[string]string{ 211 TopologyKey: TopologyValue, 212 }, 213 }, 214 } 215 } 216 } 217 218 func (s *service) findVol(k, v string) (volIdx int, volInfo csi.Volume) { 219 s.volsRWL.RLock() 220 defer s.volsRWL.RUnlock() 221 return s.findVolNoLock(k, v) 222 } 223 224 func (s *service) findVolNoLock(k, v string) (volIdx int, volInfo csi.Volume) { 225 volIdx = -1 226 227 for i, vi := range s.vols { 228 switch k { 229 case "id": 230 if strings.EqualFold(v, vi.GetVolumeId()) { 231 return i, vi 232 } 233 case "name": 234 if n, ok := vi.VolumeContext["name"]; ok && strings.EqualFold(v, n) { 235 return i, vi 236 } 237 } 238 } 239 240 return 241 } 242 243 func (s *service) findVolByName( 244 ctx context.Context, name string) (int, csi.Volume) { 245 246 return s.findVol("name", name) 247 } 248 249 func (s *service) findVolByID( 250 ctx context.Context, id string) (int, csi.Volume) { 251 252 return s.findVol("id", id) 253 } 254 255 func (s *service) newSnapshot(name, sourceVolumeId string, parameters map[string]string) cache.Snapshot { 256 257 ptime := timestamppb.Now() 258 return cache.Snapshot{ 259 Name: name, 260 Parameters: parameters, 261 SnapshotCSI: csi.Snapshot{ 262 SnapshotId: fmt.Sprintf("%d", atomic.AddUint64(&s.snapshotsNID, 1)), 263 CreationTime: ptime, 264 SourceVolumeId: sourceVolumeId, 265 ReadyToUse: true, 266 }, 267 } 268 } 269 270 // getAttachCount returns the number of attached volumes on the node. 271 func (s *service) getAttachCount(devPathKey string) int64 { 272 var count int64 273 for _, v := range s.vols { 274 if device := v.VolumeContext[devPathKey]; device != "" { 275 count++ 276 } 277 } 278 return count 279 } 280 281 func (s *service) execHook(hookName string) (codes.Code, string) { 282 return codes.OK, "" 283 }