k8s.io/kubernetes@v1.29.3/test/e2e/storage/drivers/csi-test/mock/service/node.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 "path" 22 "strconv" 23 24 "google.golang.org/grpc/codes" 25 "google.golang.org/grpc/status" 26 27 "golang.org/x/net/context" 28 29 "github.com/container-storage-interface/spec/lib/go/csi" 30 ) 31 32 func (s *service) NodeStageVolume( 33 ctx context.Context, 34 req *csi.NodeStageVolumeRequest) ( 35 *csi.NodeStageVolumeResponse, error) { 36 37 device, ok := req.PublishContext["device"] 38 if !ok { 39 if s.config.DisableAttach { 40 device = "mock device" 41 } else { 42 return nil, status.Error( 43 codes.InvalidArgument, 44 "stage volume info 'device' key required") 45 } 46 } 47 48 if len(req.GetVolumeId()) == 0 { 49 return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty") 50 } 51 52 if len(req.GetStagingTargetPath()) == 0 { 53 return nil, status.Error(codes.InvalidArgument, "Staging Target Path cannot be empty") 54 } 55 56 if req.GetVolumeCapability() == nil { 57 return nil, status.Error(codes.InvalidArgument, "Volume Capability cannot be empty") 58 } 59 60 exists, err := s.config.IO.DirExists(req.StagingTargetPath) 61 if err != nil { 62 return nil, status.Error(codes.Internal, err.Error()) 63 } 64 if !exists { 65 status.Errorf(codes.Internal, "staging target path %s does not exist", req.StagingTargetPath) 66 } 67 68 s.volsRWL.Lock() 69 defer s.volsRWL.Unlock() 70 71 i, v := s.findVolNoLock("id", req.VolumeId) 72 if i < 0 { 73 return nil, status.Error(codes.NotFound, req.VolumeId) 74 } 75 76 // nodeStgPathKey is the key in the volume's attributes that is set to a 77 // mock stage path if the volume has been published by the node 78 nodeStgPathKey := path.Join(s.nodeID, req.StagingTargetPath) 79 80 // Check to see if the volume has already been staged. 81 if v.VolumeContext[nodeStgPathKey] != "" { 82 // TODO: Check for the capabilities to be equal. Return "ALREADY_EXISTS" 83 // if the capabilities don't match. 84 return &csi.NodeStageVolumeResponse{}, nil 85 } 86 87 // Stage the volume. 88 v.VolumeContext[nodeStgPathKey] = device 89 s.vols[i] = v 90 91 if hookVal, hookMsg := s.execHook("NodeStageVolumeEnd"); hookVal != codes.OK { 92 return nil, status.Errorf(hookVal, hookMsg) 93 } 94 95 return &csi.NodeStageVolumeResponse{}, nil 96 } 97 98 func (s *service) NodeUnstageVolume( 99 ctx context.Context, 100 req *csi.NodeUnstageVolumeRequest) ( 101 *csi.NodeUnstageVolumeResponse, error) { 102 103 if len(req.GetVolumeId()) == 0 { 104 return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty") 105 } 106 107 if len(req.GetStagingTargetPath()) == 0 { 108 return nil, status.Error(codes.InvalidArgument, "Staging Target Path cannot be empty") 109 } 110 111 s.volsRWL.Lock() 112 defer s.volsRWL.Unlock() 113 114 i, v := s.findVolNoLock("id", req.VolumeId) 115 if i < 0 { 116 return nil, status.Error(codes.NotFound, req.VolumeId) 117 } 118 119 // nodeStgPathKey is the key in the volume's attributes that is set to a 120 // mock stage path if the volume has been published by the node 121 nodeStgPathKey := path.Join(s.nodeID, req.StagingTargetPath) 122 123 // Check to see if the volume has already been unstaged. 124 if v.VolumeContext[nodeStgPathKey] == "" { 125 return &csi.NodeUnstageVolumeResponse{}, nil 126 } 127 128 // Unpublish the volume. 129 delete(v.VolumeContext, nodeStgPathKey) 130 s.vols[i] = v 131 132 if hookVal, hookMsg := s.execHook("NodeUnstageVolumeEnd"); hookVal != codes.OK { 133 return nil, status.Errorf(hookVal, hookMsg) 134 } 135 return &csi.NodeUnstageVolumeResponse{}, nil 136 } 137 138 func (s *service) NodePublishVolume( 139 ctx context.Context, 140 req *csi.NodePublishVolumeRequest) ( 141 *csi.NodePublishVolumeResponse, error) { 142 143 if hookVal, hookMsg := s.execHook("NodePublishVolumeStart"); hookVal != codes.OK { 144 return nil, status.Errorf(hookVal, hookMsg) 145 } 146 ephemeralVolume := req.GetVolumeContext()["csi.storage.k8s.io/ephemeral"] == "true" 147 device, ok := req.PublishContext["device"] 148 if !ok { 149 if ephemeralVolume || s.config.DisableAttach { 150 device = "mock device" 151 } else { 152 return nil, status.Error( 153 codes.InvalidArgument, 154 "stage volume info 'device' key required") 155 } 156 } 157 158 if len(req.GetVolumeId()) == 0 { 159 return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty") 160 } 161 162 if len(req.GetTargetPath()) == 0 { 163 return nil, status.Error(codes.InvalidArgument, "Target Path cannot be empty") 164 } 165 166 if req.GetVolumeCapability() == nil { 167 return nil, status.Error(codes.InvalidArgument, "Volume Capability cannot be empty") 168 } 169 170 // May happen with old (or, at this time, even the current) Kubernetes 171 // although it shouldn't (https://github.com/kubernetes/kubernetes/issues/75535). 172 exists, err := s.config.IO.DirExists(req.TargetPath) 173 if err != nil { 174 return nil, status.Error(codes.Internal, err.Error()) 175 } 176 if !s.config.PermissiveTargetPath && exists { 177 status.Errorf(codes.Internal, "target path %s does exist", req.TargetPath) 178 } 179 180 s.volsRWL.Lock() 181 defer s.volsRWL.Unlock() 182 183 i, v := s.findVolNoLock("id", req.VolumeId) 184 if i < 0 && !ephemeralVolume { 185 return nil, status.Error(codes.NotFound, req.VolumeId) 186 } 187 if i >= 0 && ephemeralVolume { 188 return nil, status.Error(codes.AlreadyExists, req.VolumeId) 189 } 190 191 // nodeMntPathKey is the key in the volume's attributes that is set to a 192 // mock mount path if the volume has been published by the node 193 nodeMntPathKey := path.Join(s.nodeID, req.TargetPath) 194 195 // Check to see if the volume has already been published. 196 if v.VolumeContext[nodeMntPathKey] != "" { 197 198 // Requests marked Readonly fail due to volumes published by 199 // the Mock driver supporting only RW mode. 200 if req.Readonly { 201 return nil, status.Error(codes.AlreadyExists, req.VolumeId) 202 } 203 204 return &csi.NodePublishVolumeResponse{}, nil 205 } 206 207 // Publish the volume. 208 if ephemeralVolume { 209 MockVolumes[req.VolumeId] = Volume{ 210 ISEphemeral: true, 211 } 212 } else { 213 if req.GetTargetPath() != "" { 214 exists, err := s.config.IO.DirExists(req.GetTargetPath()) 215 if err != nil { 216 return nil, status.Error(codes.Internal, err.Error()) 217 } 218 if !exists { 219 // If target path does not exist we need to create the directory where volume will be staged 220 if err = s.config.IO.Mkdir(req.TargetPath); err != nil { 221 msg := fmt.Sprintf("NodePublishVolume: could not create target dir %q: %v", req.TargetPath, err) 222 return nil, status.Error(codes.Internal, msg) 223 } 224 } 225 v.VolumeContext[nodeMntPathKey] = req.GetTargetPath() 226 } else { 227 v.VolumeContext[nodeMntPathKey] = device 228 } 229 s.vols[i] = v 230 } 231 if hookVal, hookMsg := s.execHook("NodePublishVolumeEnd"); hookVal != codes.OK { 232 return nil, status.Errorf(hookVal, hookMsg) 233 } 234 235 return &csi.NodePublishVolumeResponse{}, nil 236 } 237 238 func (s *service) NodeUnpublishVolume( 239 ctx context.Context, 240 req *csi.NodeUnpublishVolumeRequest) ( 241 *csi.NodeUnpublishVolumeResponse, error) { 242 243 if len(req.GetVolumeId()) == 0 { 244 return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty") 245 } 246 if len(req.GetTargetPath()) == 0 { 247 return nil, status.Error(codes.InvalidArgument, "Target Path cannot be empty") 248 } 249 if hookVal, hookMsg := s.execHook("NodeUnpublishVolumeStart"); hookVal != codes.OK { 250 return nil, status.Errorf(hookVal, hookMsg) 251 } 252 253 s.volsRWL.Lock() 254 defer s.volsRWL.Unlock() 255 256 ephemeralVolume := MockVolumes[req.VolumeId].ISEphemeral 257 i, v := s.findVolNoLock("id", req.VolumeId) 258 if i < 0 && !ephemeralVolume { 259 return nil, status.Error(codes.NotFound, req.VolumeId) 260 } 261 262 if ephemeralVolume { 263 delete(MockVolumes, req.VolumeId) 264 } else { 265 // nodeMntPathKey is the key in the volume's attributes that is set to a 266 // mock mount path if the volume has been published by the node 267 nodeMntPathKey := path.Join(s.nodeID, req.TargetPath) 268 269 // Check to see if the volume has already been unpublished. 270 if v.VolumeContext[nodeMntPathKey] == "" { 271 return &csi.NodeUnpublishVolumeResponse{}, nil 272 } 273 274 // Delete any created paths 275 err := s.config.IO.RemoveAll(v.VolumeContext[nodeMntPathKey]) 276 if err != nil { 277 return nil, status.Errorf(codes.Internal, "Unable to delete previously created target directory") 278 } 279 280 // Unpublish the volume. 281 delete(v.VolumeContext, nodeMntPathKey) 282 s.vols[i] = v 283 } 284 if hookVal, hookMsg := s.execHook("NodeUnpublishVolumeEnd"); hookVal != codes.OK { 285 return nil, status.Errorf(hookVal, hookMsg) 286 } 287 288 return &csi.NodeUnpublishVolumeResponse{}, nil 289 } 290 291 func (s *service) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) { 292 if len(req.GetVolumeId()) == 0 { 293 return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty") 294 } 295 if len(req.GetVolumePath()) == 0 { 296 return nil, status.Error(codes.InvalidArgument, "Volume Path cannot be empty") 297 } 298 if hookVal, hookMsg := s.execHook("NodeExpandVolumeStart"); hookVal != codes.OK { 299 return nil, status.Errorf(hookVal, hookMsg) 300 } 301 302 s.volsRWL.Lock() 303 defer s.volsRWL.Unlock() 304 305 i, v := s.findVolNoLock("id", req.VolumeId) 306 if i < 0 { 307 return nil, status.Error(codes.NotFound, req.VolumeId) 308 } 309 310 // TODO: NodeExpandVolume MUST be called after successful NodeStageVolume as we has STAGE_UNSTAGE_VOLUME node capacity. 311 resp := &csi.NodeExpandVolumeResponse{} 312 var requestCapacity int64 = 0 313 if req.GetCapacityRange() != nil { 314 requestCapacity = req.CapacityRange.GetRequiredBytes() 315 resp.CapacityBytes = requestCapacity 316 } 317 318 // fsCapacityKey is the key in the volume's attributes that is set to the file system's size. 319 fsCapacityKey := path.Join(s.nodeID, req.GetVolumePath(), "size") 320 // Update volume's fs capacity to requested size. 321 if requestCapacity > 0 { 322 v.VolumeContext[fsCapacityKey] = strconv.FormatInt(requestCapacity, 10) 323 s.vols[i] = v 324 } 325 if hookVal, hookMsg := s.execHook("NodeExpandVolumeEnd"); hookVal != codes.OK { 326 return nil, status.Errorf(hookVal, hookMsg) 327 } 328 329 return resp, nil 330 } 331 332 func (s *service) NodeGetCapabilities( 333 ctx context.Context, 334 req *csi.NodeGetCapabilitiesRequest) ( 335 *csi.NodeGetCapabilitiesResponse, error) { 336 337 if hookVal, hookMsg := s.execHook("NodeGetCapabilities"); hookVal != codes.OK { 338 return nil, status.Errorf(hookVal, hookMsg) 339 } 340 capabilities := []*csi.NodeServiceCapability{ 341 { 342 Type: &csi.NodeServiceCapability_Rpc{ 343 Rpc: &csi.NodeServiceCapability_RPC{ 344 Type: csi.NodeServiceCapability_RPC_UNKNOWN, 345 }, 346 }, 347 }, 348 { 349 Type: &csi.NodeServiceCapability_Rpc{ 350 Rpc: &csi.NodeServiceCapability_RPC{ 351 Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, 352 }, 353 }, 354 }, 355 { 356 Type: &csi.NodeServiceCapability_Rpc{ 357 Rpc: &csi.NodeServiceCapability_RPC{ 358 Type: csi.NodeServiceCapability_RPC_GET_VOLUME_STATS, 359 }, 360 }, 361 }, 362 { 363 Type: &csi.NodeServiceCapability_Rpc{ 364 Rpc: &csi.NodeServiceCapability_RPC{ 365 Type: csi.NodeServiceCapability_RPC_VOLUME_CONDITION, 366 }, 367 }, 368 }, 369 } 370 if s.config.NodeExpansionRequired { 371 capabilities = append(capabilities, &csi.NodeServiceCapability{ 372 Type: &csi.NodeServiceCapability_Rpc{ 373 Rpc: &csi.NodeServiceCapability_RPC{ 374 Type: csi.NodeServiceCapability_RPC_EXPAND_VOLUME, 375 }, 376 }, 377 }) 378 } 379 380 if s.config.VolumeMountGroupRequired { 381 capabilities = append(capabilities, &csi.NodeServiceCapability{ 382 Type: &csi.NodeServiceCapability_Rpc{ 383 Rpc: &csi.NodeServiceCapability_RPC{ 384 Type: csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP, 385 }, 386 }, 387 }) 388 } 389 390 return &csi.NodeGetCapabilitiesResponse{ 391 Capabilities: capabilities, 392 }, nil 393 } 394 395 func (s *service) NodeGetInfo(ctx context.Context, 396 req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) { 397 if hookVal, hookMsg := s.execHook("NodeGetInfo"); hookVal != codes.OK { 398 return nil, status.Errorf(hookVal, hookMsg) 399 } 400 csiNodeResponse := &csi.NodeGetInfoResponse{ 401 NodeId: s.nodeID, 402 } 403 if s.config.AttachLimit > 0 { 404 csiNodeResponse.MaxVolumesPerNode = s.config.AttachLimit 405 } 406 if s.config.EnableTopology { 407 csiNodeResponse.AccessibleTopology = &csi.Topology{ 408 Segments: map[string]string{ 409 TopologyKey: TopologyValue, 410 }, 411 } 412 } 413 return csiNodeResponse, nil 414 } 415 416 func (s *service) NodeGetVolumeStats(ctx context.Context, 417 req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) { 418 419 resp := &csi.NodeGetVolumeStatsResponse{ 420 VolumeCondition: &csi.VolumeCondition{}, 421 } 422 423 if len(req.GetVolumeId()) == 0 { 424 return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty") 425 } 426 427 if len(req.GetVolumePath()) == 0 { 428 return nil, status.Error(codes.InvalidArgument, "Volume Path cannot be empty") 429 } 430 431 i, v := s.findVolNoLock("id", req.VolumeId) 432 if i < 0 { 433 resp.VolumeCondition.Abnormal = true 434 resp.VolumeCondition.Message = "Volume not found" 435 return resp, status.Error(codes.NotFound, req.VolumeId) 436 } 437 438 nodeMntPathKey := path.Join(s.nodeID, req.VolumePath) 439 440 _, exists := v.VolumeContext[nodeMntPathKey] 441 if !exists { 442 msg := fmt.Sprintf("volume %q doest not exist on the specified path %q", req.VolumeId, req.VolumePath) 443 resp.VolumeCondition.Abnormal = true 444 resp.VolumeCondition.Message = msg 445 return resp, status.Errorf(codes.NotFound, msg) 446 } 447 448 if hookVal, hookMsg := s.execHook("NodeGetVolumeStatsEnd"); hookVal != codes.OK { 449 return nil, status.Errorf(hookVal, hookMsg) 450 } 451 452 resp.Usage = []*csi.VolumeUsage{ 453 { 454 Total: v.GetCapacityBytes(), 455 Unit: csi.VolumeUsage_BYTES, 456 }, 457 } 458 459 return resp, nil 460 }