github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/jrpcfs/middleware.go (about) 1 // Copyright (c) 2015-2021, NVIDIA CORPORATION. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package jrpcfs 5 6 import ( 7 "fmt" 8 9 "github.com/swiftstack/ProxyFS/blunder" 10 "github.com/swiftstack/ProxyFS/fs" 11 "github.com/swiftstack/ProxyFS/inode" 12 "github.com/swiftstack/ProxyFS/logger" 13 "github.com/swiftstack/ProxyFS/utils" 14 ) 15 16 // JSON RPC Server on top of the FS package. 17 // 18 // This file handles the RPCs related to Swift middleware support 19 // 20 // NOTE: These functions should only verify the arguments and then call 21 // functions in package fs since there may be fs locking required. 22 23 // parseVirtPath extracts path components and fetches the corresponding fs.VolumeHandle from virtPath 24 func parseVirtPath(virtPath string) (accountName string, vContainerName string, vObjectName string, volumeName string, volumeHandle fs.VolumeHandle, err error) { 25 26 // Extract Account, vContainer, and vObject from VirtPath 27 accountName, vContainerName, vObjectName, err = utils.PathToAcctContObj(virtPath) 28 if nil != err { 29 return 30 } 31 32 // Map accountName to volumeHandle 33 volumeHandle, err = fs.FetchVolumeHandleByAccountName(accountName) 34 if nil != err { 35 return 36 } 37 38 // Map volumeHandle to volumeName 39 volumeName = volumeHandle.VolumeName() 40 41 return 42 } 43 44 // RpcCreateContainer is used by Middleware to PUT of a container. 45 // 46 // TODO - add update of metadata 47 // TODO - combine this into one PUT RPC instead of multiple RPCs? 48 func (s *Server) RpcCreateContainer(in *CreateContainerRequest, reply *CreateContainerReply) (err error) { 49 flog := logger.TraceEnter("in.", in) 50 defer func() { flog.TraceExitErr("reply.", err, reply) }() 51 // TODO: Need to determine how we want to pass errors back for RPCs used by middleware. 52 // By default, jrpcfs code (and rpcEncodeError) use errno-type errors. 53 // However for RPCs used by middleware, perhaps we want to return HTTP status codes? 54 // The blunder error package supports this, we just need to add some helper functions. 55 //defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 56 57 accountName, containerName, _, _, volumeHandle, err := parseVirtPath(in.VirtPath) 58 59 // Validate the components of the containerName 60 err = fs.ValidateFullPath(containerName) 61 if err != nil { 62 return err 63 } 64 65 err = fs.ValidateBaseName(containerName) 66 if err != nil { 67 return err 68 } 69 70 // Make the directory 71 _, err = volumeHandle.Mkdir(inode.InodeRootUserID, inode.InodeGroupID(0), nil, inode.RootDirInodeNumber, containerName, inode.PosixModePerm) 72 73 if err != nil { 74 logger.DebugfIDWithError(internalDebug, err, "fs.Mkdir() of acct: %v vContainerName: %v failed!", accountName, containerName) 75 } 76 return 77 } 78 79 // RpcDelete is used by Middleware to service a DELETE HTTP request. 80 // 81 // This routine has to handle delete of an empty container as well as delete of an objectName 82 // where objectName could be "file1", "dir1/file1", "dir1/dir2", etc. 83 func (s *Server) RpcDelete(in *DeleteReq, reply *DeleteReply) (err error) { 84 flog := logger.TraceEnter("in.", in) 85 defer func() { flog.TraceExitErr("reply.", err, reply) }() 86 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 87 88 _, containerName, objectName, _, volumeHandle, err := parseVirtPath(in.VirtPath) 89 90 parentDir, baseName := splitPath(containerName + "/" + objectName) 91 92 // objectName empty means we are deleting a container 93 if objectName == "" { 94 parentDir = "/" 95 baseName = containerName 96 } 97 98 // Call fs to delete the baseName if it is a file or an empty directory. 99 err = volumeHandle.MiddlewareDelete(parentDir, baseName) 100 101 return err 102 } 103 104 // RpcGetAccount is used by Middleware to issue a GET on an account and return the results. 105 func (s *Server) RpcGetAccount(in *GetAccountReq, reply *GetAccountReply) (err error) { 106 flog := logger.TraceEnter("in.", in) 107 defer func() { flog.TraceExitErr("reply.", err, reply) }() 108 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 109 110 _, _, _, _, volumeHandle, err := parseVirtPath(in.VirtPath) 111 if err != nil { 112 logger.ErrorfWithError(err, "RpcGetAccount: error mounting share for %s", in.VirtPath) 113 return err 114 } 115 116 entries, mtime, ctime, err := volumeHandle.MiddlewareGetAccount(in.MaxEntries, in.Marker, in.EndMarker) 117 if err != nil { 118 return err 119 } 120 reply.AccountEntries = entries 121 reply.ModificationTime = mtime 122 reply.AttrChangeTime = ctime 123 return nil 124 } 125 126 // RpcHead is used by Middleware to issue a HEAD on a container or object and return the results. 127 func (s *Server) RpcHead(in *HeadReq, reply *HeadReply) (err error) { 128 flog := logger.TraceEnter("in.", in) 129 defer func() { flog.TraceExitErr("reply.", err, reply) }() 130 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 131 132 _, vContainerName, vObjectName, _, volumeHandle, err := parseVirtPath(in.VirtPath) 133 if err != nil { 134 logger.ErrorfWithError(err, "RpcHead: error mounting share for %s", in.VirtPath) 135 return err 136 } 137 138 entityPath := vContainerName 139 if vObjectName != "" { 140 entityPath = entityPath + "/" + vObjectName 141 } 142 143 resp, err := volumeHandle.MiddlewareHeadResponse(entityPath) 144 if err != nil { 145 if !blunder.Is(err, blunder.NotFoundError) { 146 logger.ErrorfWithError(err, "RpcHead: error retrieving metadata for %s", in.VirtPath) 147 } 148 return err 149 } 150 151 reply.Metadata = resp.Metadata 152 reply.FileSize = resp.FileSize 153 reply.ModificationTime = resp.ModificationTime 154 reply.AttrChangeTime = resp.AttrChangeTime 155 reply.InodeNumber = int64(uint64(resp.InodeNumber)) 156 reply.NumWrites = resp.NumWrites 157 reply.IsDir = resp.IsDir 158 159 return nil 160 } 161 162 // RpcGetContainer is used by Middleware to issue a GET on a container and return the results. 163 func (s *Server) RpcGetContainer(in *GetContainerReq, reply *GetContainerReply) (err error) { 164 flog := logger.TraceEnter("in.", in) 165 defer func() { flog.TraceExitErr("reply.", err, reply) }() 166 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 167 168 _, vContainerName, _, _, volumeHandle, err := parseVirtPath(in.VirtPath) 169 if err != nil { 170 logger.ErrorfWithError(err, "RpcGetContainer: error mounting share for %s", in.VirtPath) 171 return err 172 } 173 174 entries, err := volumeHandle.MiddlewareGetContainer(vContainerName, in.MaxEntries, in.Marker, in.EndMarker, in.Prefix, in.Delimiter) 175 if err != nil { 176 return err 177 } 178 resp, err := volumeHandle.MiddlewareHeadResponse(vContainerName) 179 if err != nil { 180 return err 181 } 182 reply.ContainerEntries = entries 183 reply.Metadata = resp.Metadata 184 reply.ModificationTime = resp.ModificationTime 185 return nil 186 } 187 188 // RpcGetObject is used by GET HTTP request to retrieve the read plan for an object. 189 func (s *Server) RpcGetObject(in *GetObjectReq, reply *GetObjectReply) (err error) { 190 191 flog := logger.TraceEnter("in.", in) 192 defer func() { flog.TraceExitErr("reply.", err, reply) }() 193 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 194 195 _, vContainerName, objectName, _, volumeHandle, err := parseVirtPath(in.VirtPath) 196 197 mountRelativePath := vContainerName + "/" + objectName 198 199 resp, err := volumeHandle.MiddlewareGetObject(mountRelativePath, in.ReadEntsIn, &reply.ReadEntsOut) 200 if err != nil { 201 if !blunder.Is(err, blunder.NotFoundError) { 202 logger.ErrorfWithError(err, "RpcGetObject(): error retrieving metadata for %s", in.VirtPath) 203 } 204 return err 205 } 206 207 reply.Metadata = resp.Metadata 208 reply.FileSize = resp.FileSize 209 reply.ModificationTime = resp.ModificationTime 210 reply.AttrChangeTime = resp.AttrChangeTime 211 reply.InodeNumber = uint64(resp.InodeNumber) 212 reply.NumWrites = resp.NumWrites 213 reply.IsDir = resp.IsDir 214 215 return err 216 } 217 218 // RpcPost handles a POST command from middleware for an account, container or object. 219 func (s *Server) RpcPost(in *MiddlewarePostReq, reply *MiddlewarePostReply) (err error) { 220 flog := logger.TraceEnter("in.", in) 221 defer func() { flog.TraceExitErr("reply.", err, reply) }() 222 223 accountName, containerName, objectName, _, volumeHandle, err := parseVirtPath(in.VirtPath) 224 225 // Don't allow a POST on an invalid account or on just an account 226 if accountName == "" || containerName == "" { 227 err = fmt.Errorf("%s: Can't modify an account, AccountName: %v is invalid or ContainerName: %v is invalid.", utils.GetFnName(), accountName, containerName) 228 logger.ErrorWithError(err) 229 err = blunder.AddError(err, blunder.AccountNotModifiable) 230 return err 231 } 232 233 var parentDir, baseName string 234 if objectName != "" { 235 parentDir, baseName = splitPath(containerName + "/" + objectName) 236 } else { 237 parentDir, baseName = splitPath(containerName) 238 } 239 240 err = volumeHandle.MiddlewarePost(parentDir, baseName, in.NewMetaData, in.OldMetaData) 241 242 return err 243 244 } 245 246 // Makes a directory. Unlike RpcMkdir, one can invoke this with just a path. 247 func (s *Server) RpcMiddlewareMkdir(in *MiddlewareMkdirReq, reply *MiddlewareMkdirReply) (err error) { 248 flog := logger.TraceEnter("in.", in) 249 defer func() { flog.TraceExitErr("reply.", err, reply) }() 250 251 _, containerName, objectName, _, volumeHandle, err := parseVirtPath(in.VirtPath) 252 253 // Require a reference to an object; you can't create a container with this method. 254 if objectName == "" { 255 err = blunder.NewError(blunder.NotAnObjectError, "%s: VirtPath must reference an object, not container or account (%s)", utils.GetFnName(), in.VirtPath) 256 // This is worth logging; a correct middleware will never send such a path. 257 logger.ErrorWithError(err) 258 return err 259 } 260 261 mtime, ctime, inodeNumber, numWrites, err := volumeHandle.MiddlewareMkdir(containerName, objectName, in.Metadata) 262 reply.ModificationTime = mtime 263 reply.AttrChangeTime = ctime 264 reply.InodeNumber = int64(uint64(inodeNumber)) 265 reply.NumWrites = numWrites 266 return 267 } 268 269 // RpcPutComplete is used by PUT HTTP request once data has been put in Swift. 270 // 271 // Sets up inode, etc. 272 func (s *Server) RpcPutComplete(in *PutCompleteReq, reply *PutCompleteReply) (err error) { 273 flog := logger.TraceEnter("in.", in) 274 defer func() { flog.TraceExitErr("reply.", err, reply) }() 275 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 276 277 _, containerName, objectName, _, volumeHandle, err := parseVirtPath(in.VirtPath) 278 279 // Call fs to complete the creation of the inode for the file and 280 // the directories. 281 mtime, ctime, ino, numWrites, err := volumeHandle.MiddlewarePutComplete(containerName, objectName, in.PhysPaths, in.PhysLengths, in.Metadata) 282 reply.ModificationTime = mtime 283 reply.AttrChangeTime = ctime 284 reply.InodeNumber = int64(uint64(ino)) 285 reply.NumWrites = numWrites 286 287 return err 288 } 289 290 // RpcPutLocation is used by PUT HTTP request to provision an object so that middleware 291 // can PUT the object in Swift. 292 // 293 // Later, a RpcPutComplete() will be called to setup inode, etc. 294 func (s *Server) RpcPutLocation(in *PutLocationReq, reply *PutLocationReply) (err error) { 295 enterGate() 296 defer leaveGate() 297 298 flog := logger.TraceEnter("in.", in) 299 defer func() { flog.TraceExitErr("reply.", err, reply) }() 300 301 accountName, containerName, objectName, _, volumeHandle, err := parseVirtPath(in.VirtPath) 302 303 // Validate the components of the objectName 304 err = fs.ValidateFullPath(containerName + "/" + objectName) 305 if err != nil { 306 return err 307 } 308 309 _, baseName := splitPath(containerName + "/" + objectName) 310 err = fs.ValidateBaseName(baseName) 311 if err != nil { 312 return err 313 } 314 315 // Via fs package, ask inode package to provision object 316 reply.PhysPath, err = volumeHandle.CallInodeToProvisionObject() 317 318 if err != nil { 319 logger.DebugfIDWithError(internalDebug, err, "fs.CallInodeToProvisionObject() of acct: %v container: %v failed!", accountName, containerName) 320 } 321 return 322 } 323 324 // RpcPutContainer creates or updates a container (top-level directory). 325 func (s *Server) RpcPutContainer(in *PutContainerReq, reply *PutContainerReply) (err error) { 326 enterGate() 327 defer leaveGate() 328 329 flog := logger.TraceEnter("in.", in) 330 defer func() { flog.TraceExitErr("reply.", err, reply) }() 331 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 332 333 _, containerName, _, _, volumeHandle, err := parseVirtPath(in.VirtPath) 334 if err != nil { 335 return err 336 } 337 338 err = volumeHandle.MiddlewarePutContainer(containerName, in.OldMetadata, in.NewMetadata) 339 return err 340 } 341 342 // Combine a bunch of files together into a big one. It's like "cat old1 old2 ... > new", but without the cat. Also 343 // removes the files old1 old2 ... 344 func (s *Server) RpcCoalesce(in *CoalesceReq, reply *CoalesceReply) (err error) { 345 enterGate() 346 defer leaveGate() 347 348 flog := logger.TraceEnter("in.", in) 349 defer func() { flog.TraceExitErr("reply.", err, reply) }() 350 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 351 352 _, destContainer, destObject, _, volumeHandle, err := parseVirtPath(in.VirtPath) 353 354 var ino uint64 355 ino, reply.NumWrites, reply.AttrChangeTime, reply.ModificationTime, err = 356 volumeHandle.MiddlewareCoalesce( 357 destContainer+"/"+destObject, in.NewMetaData, in.ElementAccountRelativePaths) 358 reply.InodeNumber = int64(ino) 359 return 360 } 361 362 // Renew a lease, ensuring that the related file's log segments won't get deleted. This ensures that an HTTP client is 363 // able to complete an object GET request regardless of concurrent FS writes or HTTP PUTs to that file. 364 // 365 // Middleware calls this periodically while producing an object GET response. 366 func (s *Server) RpcRenewLease(in *RenewLeaseReq, reply *RenewLeaseReply) (err error) { 367 enterGate() 368 defer leaveGate() 369 370 flog := logger.TraceEnter("in.", in) 371 defer func() { flog.TraceExitErr("reply.", err, reply) }() 372 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 373 374 // This is currently a stub, as there's not yet any idea of a lease, so there's nothing to renew. 375 return 376 } 377 378 // Release a lease, allowing a file's log segments to be deleted when necessary. 379 // 380 // Middleware calls this once an object GET response is complete. 381 func (s *Server) RpcReleaseLease(in *ReleaseLeaseReq, reply *ReleaseLeaseReply) (err error) { 382 enterGate() 383 defer leaveGate() 384 385 flog := logger.TraceEnter("in.", in) 386 defer func() { flog.TraceExitErr("reply.", err, reply) }() 387 defer func() { rpcEncodeError(&err) }() // Encode error for return by RPC 388 389 // This is currently a stub, as there's not yet any idea of a lease, so there's nothing to release. 390 return 391 } 392 393 // RpcIsAccountBimodal answers the question "is this account known by ProxyFS to be bimodal?". 394 // 395 // If bimodal, also indicates the PrivateIPAddr of the Peer ProxyFS instance serving the matching volume 396 func (s *Server) RpcIsAccountBimodal(in *IsAccountBimodalReq, reply *IsAccountBimodalReply) (err error) { 397 var ( 398 ok bool 399 volumeName string 400 ) 401 402 enterGate() 403 defer leaveGate() 404 405 flog := logger.TraceEnter("in.", in) 406 defer func() { flog.TraceExitErr("reply.", err, reply) }() 407 408 volumeName, reply.IsBimodal = fs.AccountNameToVolumeName(in.AccountName) 409 410 if reply.IsBimodal { 411 reply.ActivePeerPrivateIPAddr, ok = fs.VolumeNameToActivePeerPrivateIPAddr(volumeName) 412 if !ok { 413 err = fmt.Errorf("%v indicated as Bimodal but no matching ActivePeer", volumeName) 414 logger.ErrorWithError(err) 415 return 416 } 417 } 418 419 err = nil 420 return 421 }