github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/client/allocrunner/csi_hook.go (about)

     1  package allocrunner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	hclog "github.com/hashicorp/go-hclog"
     8  	"github.com/hashicorp/nomad/client/pluginmanager/csimanager"
     9  	"github.com/hashicorp/nomad/nomad/structs"
    10  )
    11  
    12  // csiHook will wait for remote csi volumes to be attached to the host before
    13  // continuing.
    14  //
    15  // It is a noop for allocs that do not depend on CSI Volumes.
    16  type csiHook struct {
    17  	alloc      *structs.Allocation
    18  	logger     hclog.Logger
    19  	csimanager csimanager.Manager
    20  	rpcClient  RPCer
    21  	updater    hookResourceSetter
    22  }
    23  
    24  func (c *csiHook) Name() string {
    25  	return "csi_hook"
    26  }
    27  
    28  func (c *csiHook) Prerun() error {
    29  	if !c.shouldRun() {
    30  		return nil
    31  	}
    32  
    33  	// TODO(tgross): the contexts for the CSI RPC calls made during
    34  	// mounting can have very long timeouts. Until these are better
    35  	// tuned, there's not a good value to put here for a WithCancel
    36  	// without risking conflicts with the grpc retries/timeouts in the
    37  	// pluginmanager package.
    38  	ctx := context.TODO()
    39  
    40  	volumes, err := c.claimVolumesFromAlloc()
    41  	if err != nil {
    42  		return fmt.Errorf("claim volumes: %v", err)
    43  	}
    44  
    45  	mounts := make(map[string]*csimanager.MountInfo, len(volumes))
    46  	for alias, pair := range volumes {
    47  		mounter, err := c.csimanager.MounterForPlugin(ctx, pair.volume.PluginID)
    48  		if err != nil {
    49  			return err
    50  		}
    51  
    52  		usageOpts := &csimanager.UsageOptions{
    53  			ReadOnly:       pair.request.ReadOnly,
    54  			AttachmentMode: string(pair.volume.AttachmentMode),
    55  			AccessMode:     string(pair.volume.AccessMode),
    56  			MountOptions:   pair.request.MountOptions,
    57  		}
    58  
    59  		mountInfo, err := mounter.MountVolume(ctx, pair.volume, c.alloc, usageOpts, pair.publishContext)
    60  		if err != nil {
    61  			return err
    62  		}
    63  
    64  		mounts[alias] = mountInfo
    65  	}
    66  
    67  	res := c.updater.GetAllocHookResources()
    68  	res.CSIMounts = mounts
    69  	c.updater.SetAllocHookResources(res)
    70  
    71  	return nil
    72  }
    73  
    74  type volumeAndRequest struct {
    75  	volume  *structs.CSIVolume
    76  	request *structs.VolumeRequest
    77  
    78  	// When volumeAndRequest was returned from a volume claim, this field will be
    79  	// populated for plugins that require it.
    80  	publishContext map[string]string
    81  }
    82  
    83  // claimVolumesFromAlloc is used by the pre-run hook to fetch all of the volume
    84  // metadata and claim it for use by this alloc/node at the same time.
    85  func (c *csiHook) claimVolumesFromAlloc() (map[string]*volumeAndRequest, error) {
    86  	result := make(map[string]*volumeAndRequest)
    87  	tg := c.alloc.Job.LookupTaskGroup(c.alloc.TaskGroup)
    88  
    89  	// Initially, populate the result map with all of the requests
    90  	for alias, volumeRequest := range tg.Volumes {
    91  		if volumeRequest.Type == structs.VolumeTypeCSI {
    92  			result[alias] = &volumeAndRequest{request: volumeRequest}
    93  		}
    94  	}
    95  
    96  	// Iterate over the result map and upsert the volume field as each volume gets
    97  	// claimed by the server.
    98  	for alias, pair := range result {
    99  		claimType := structs.CSIVolumeClaimWrite
   100  		if pair.request.ReadOnly {
   101  			claimType = structs.CSIVolumeClaimRead
   102  		}
   103  
   104  		req := &structs.CSIVolumeClaimRequest{
   105  			VolumeID:     pair.request.Source,
   106  			AllocationID: c.alloc.ID,
   107  			NodeID:       c.alloc.NodeID,
   108  			Claim:        claimType,
   109  		}
   110  		req.Region = c.alloc.Job.Region
   111  
   112  		var resp structs.CSIVolumeClaimResponse
   113  		if err := c.rpcClient.RPC("CSIVolume.Claim", req, &resp); err != nil {
   114  			return nil, err
   115  		}
   116  
   117  		if resp.Volume == nil {
   118  			return nil, fmt.Errorf("Unexpected nil volume returned for ID: %v", pair.request.Source)
   119  		}
   120  
   121  		result[alias].volume = resp.Volume
   122  		result[alias].publishContext = resp.PublishContext
   123  	}
   124  
   125  	return result, nil
   126  }
   127  
   128  func newCSIHook(logger hclog.Logger, alloc *structs.Allocation, rpcClient RPCer, csi csimanager.Manager, updater hookResourceSetter) *csiHook {
   129  	return &csiHook{
   130  		alloc:      alloc,
   131  		logger:     logger.Named("csi_hook"),
   132  		rpcClient:  rpcClient,
   133  		csimanager: csi,
   134  		updater:    updater,
   135  	}
   136  }
   137  
   138  func (h *csiHook) shouldRun() bool {
   139  	tg := h.alloc.Job.LookupTaskGroup(h.alloc.TaskGroup)
   140  	for _, vol := range tg.Volumes {
   141  		if vol.Type == structs.VolumeTypeCSI {
   142  			return true
   143  		}
   144  	}
   145  
   146  	return false
   147  }