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  }