github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/client/node_updater.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/hashicorp/nomad/client/devicemanager"
    10  	"github.com/hashicorp/nomad/client/pluginmanager/drivermanager"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  )
    13  
    14  var (
    15  	// batchFirstFingerprintsTimeout is the maximum amount of time to wait for
    16  	// initial fingerprinting to complete before sending a batched Node update
    17  	batchFirstFingerprintsTimeout = 50 * time.Second
    18  )
    19  
    20  // batchFirstFingerprints waits for the first fingerprint response from all
    21  // plugin managers and sends a single Node update for all fingerprints. It
    22  // should only ever be called once
    23  func (c *Client) batchFirstFingerprints() {
    24  	ctx, cancel := context.WithTimeout(context.Background(), batchFirstFingerprintsTimeout)
    25  	defer cancel()
    26  
    27  	ch, err := c.pluginManagers.WaitForFirstFingerprint(ctx)
    28  	if err != nil {
    29  		c.logger.Warn("failed to batch initial fingerprint updates, switching to incemental updates")
    30  		goto SEND_BATCH
    31  	}
    32  
    33  	// Wait for fingerprinting to complete or timeout before processing batches
    34  	select {
    35  	case <-ch:
    36  	case <-ctx.Done():
    37  	}
    38  
    39  SEND_BATCH:
    40  	c.configLock.Lock()
    41  	defer c.configLock.Unlock()
    42  
    43  	// driver node updates
    44  	var driverChanged bool
    45  	c.batchNodeUpdates.batchDriverUpdates(func(driver string, info *structs.DriverInfo) {
    46  		if c.updateNodeFromDriverLocked(driver, info) {
    47  			c.config.Node.Drivers[driver] = info
    48  			if c.config.Node.Drivers[driver].UpdateTime.IsZero() {
    49  				c.config.Node.Drivers[driver].UpdateTime = time.Now()
    50  			}
    51  			driverChanged = true
    52  		}
    53  	})
    54  
    55  	// device node updates
    56  	var devicesChanged bool
    57  	c.batchNodeUpdates.batchDevicesUpdates(func(devices []*structs.NodeDeviceResource) {
    58  		if c.updateNodeFromDevicesLocked(devices) {
    59  			devicesChanged = true
    60  		}
    61  	})
    62  
    63  	// only update the node if changes occurred
    64  	if driverChanged || devicesChanged {
    65  		c.updateNodeLocked()
    66  	}
    67  
    68  	close(c.fpInitialized)
    69  }
    70  
    71  // updateNodeFromDriver receives a DriverInfo struct for the driver and updates
    72  // the node accordingly
    73  func (c *Client) updateNodeFromDriver(name string, info *structs.DriverInfo) {
    74  	c.configLock.Lock()
    75  	defer c.configLock.Unlock()
    76  
    77  	if c.updateNodeFromDriverLocked(name, info) {
    78  		c.config.Node.Drivers[name] = info
    79  		if c.config.Node.Drivers[name].UpdateTime.IsZero() {
    80  			c.config.Node.Drivers[name].UpdateTime = time.Now()
    81  		}
    82  		c.updateNodeLocked()
    83  	}
    84  }
    85  
    86  // updateNodeFromDriverLocked makes the changes to the node from a driver update
    87  // but does not send the update to the server. c.configLock must be held before
    88  // calling this func
    89  func (c *Client) updateNodeFromDriverLocked(name string, info *structs.DriverInfo) bool {
    90  	var hasChanged bool
    91  
    92  	hadDriver := c.config.Node.Drivers[name] != nil
    93  	if !hadDriver {
    94  		// If the driver info has not yet been set, do that here
    95  		hasChanged = true
    96  		for attrName, newVal := range info.Attributes {
    97  			c.config.Node.Attributes[attrName] = newVal
    98  		}
    99  	} else {
   100  		oldVal := c.config.Node.Drivers[name]
   101  		// The driver info has already been set, fix it up
   102  		if oldVal.Detected != info.Detected {
   103  			hasChanged = true
   104  		}
   105  
   106  		// If health state has change, trigger node event
   107  		if oldVal.Healthy != info.Healthy || oldVal.HealthDescription != info.HealthDescription {
   108  			hasChanged = true
   109  			if info.HealthDescription != "" {
   110  				event := &structs.NodeEvent{
   111  					Subsystem: "Driver",
   112  					Message:   info.HealthDescription,
   113  					Timestamp: time.Now(),
   114  					Details:   map[string]string{"driver": name},
   115  				}
   116  				c.triggerNodeEvent(event)
   117  			}
   118  		}
   119  
   120  		for attrName, newVal := range info.Attributes {
   121  			oldVal := c.config.Node.Drivers[name].Attributes[attrName]
   122  			if oldVal == newVal {
   123  				continue
   124  			}
   125  
   126  			hasChanged = true
   127  			if newVal == "" {
   128  				delete(c.config.Node.Attributes, attrName)
   129  			} else {
   130  				c.config.Node.Attributes[attrName] = newVal
   131  			}
   132  		}
   133  	}
   134  
   135  	// COMPAT Remove in Nomad 0.10
   136  	// We maintain the driver enabled attribute until all drivers expose
   137  	// their attributes as DriverInfo
   138  	driverName := fmt.Sprintf("driver.%s", name)
   139  	if info.Detected {
   140  		c.config.Node.Attributes[driverName] = "1"
   141  	} else {
   142  		delete(c.config.Node.Attributes, driverName)
   143  	}
   144  
   145  	return hasChanged
   146  }
   147  
   148  // updateNodeFromFingerprint updates the node with the result of
   149  // fingerprinting the node from the diff that was created
   150  func (c *Client) updateNodeFromDevices(devices []*structs.NodeDeviceResource) {
   151  	c.configLock.Lock()
   152  	defer c.configLock.Unlock()
   153  
   154  	// Not updating node.Resources: the field is deprecated and includes
   155  	// dispatched task resources and not appropriate for expressing
   156  	// node available device resources
   157  	if c.updateNodeFromDevicesLocked(devices) {
   158  		c.updateNodeLocked()
   159  	}
   160  }
   161  
   162  // updateNodeFromDevicesLocked updates the node with the results of devices,
   163  // but does send the update to the server. c.configLock must be held before
   164  // calling this func
   165  func (c *Client) updateNodeFromDevicesLocked(devices []*structs.NodeDeviceResource) bool {
   166  	if !structs.DevicesEquals(c.config.Node.NodeResources.Devices, devices) {
   167  		c.logger.Debug("new devices detected", "devices", len(devices))
   168  		c.config.Node.NodeResources.Devices = devices
   169  		return true
   170  	}
   171  
   172  	return false
   173  }
   174  
   175  // batchNodeUpdates allows for batching multiple Node updates from fingerprinting.
   176  // Once ready, the batches can be flushed and toggled to stop batching and forward
   177  // all updates to a configured callback to be performed incrementally
   178  type batchNodeUpdates struct {
   179  	// access to driver fields must hold driversMu lock
   180  	drivers        map[string]*structs.DriverInfo
   181  	driversBatched bool
   182  	driverCB       drivermanager.UpdateNodeDriverInfoFn
   183  	driversMu      sync.Mutex
   184  
   185  	// access to devices fields must hold devicesMu lock
   186  	devices        []*structs.NodeDeviceResource
   187  	devicesBatched bool
   188  	devicesCB      devicemanager.UpdateNodeDevicesFn
   189  	devicesMu      sync.Mutex
   190  }
   191  
   192  func newBatchNodeUpdates(
   193  	driverCB drivermanager.UpdateNodeDriverInfoFn,
   194  	devicesCB devicemanager.UpdateNodeDevicesFn) *batchNodeUpdates {
   195  
   196  	return &batchNodeUpdates{
   197  		drivers:   make(map[string]*structs.DriverInfo),
   198  		driverCB:  driverCB,
   199  		devices:   []*structs.NodeDeviceResource{},
   200  		devicesCB: devicesCB,
   201  	}
   202  }
   203  
   204  // updateNodeFromDriver implements drivermanager.UpdateNodeDriverInfoFn and is
   205  // used in the driver manager to send driver fingerprints to
   206  func (b *batchNodeUpdates) updateNodeFromDriver(driver string, info *structs.DriverInfo) {
   207  	b.driversMu.Lock()
   208  	defer b.driversMu.Unlock()
   209  	if b.driversBatched {
   210  		b.driverCB(driver, info)
   211  		return
   212  	}
   213  
   214  	b.drivers[driver] = info
   215  }
   216  
   217  // batchDriverUpdates sends all of the batched driver node updates by calling f
   218  // for each driver batched
   219  func (b *batchNodeUpdates) batchDriverUpdates(f drivermanager.UpdateNodeDriverInfoFn) error {
   220  	b.driversMu.Lock()
   221  	defer b.driversMu.Unlock()
   222  	if b.driversBatched {
   223  		return fmt.Errorf("driver updates already batched")
   224  	}
   225  
   226  	b.driversBatched = true
   227  	for driver, info := range b.drivers {
   228  		f(driver, info)
   229  	}
   230  	return nil
   231  }
   232  
   233  // updateNodeFromDevices implements devicemanager.UpdateNodeDevicesFn and is
   234  // used in the device manager to send device fingerprints to
   235  func (b *batchNodeUpdates) updateNodeFromDevices(devices []*structs.NodeDeviceResource) {
   236  	b.devicesMu.Lock()
   237  	defer b.devicesMu.Unlock()
   238  	if b.devicesBatched {
   239  		b.devicesCB(devices)
   240  		return
   241  	}
   242  
   243  	b.devices = devices
   244  }
   245  
   246  // batchDevicesUpdates sends the batched device node updates by calling f with
   247  // the devices
   248  func (b *batchNodeUpdates) batchDevicesUpdates(f devicemanager.UpdateNodeDevicesFn) error {
   249  	b.devicesMu.Lock()
   250  	defer b.devicesMu.Unlock()
   251  	if b.devicesBatched {
   252  		return fmt.Errorf("devices updates already batched")
   253  	}
   254  
   255  	b.devicesBatched = true
   256  	f(b.devices)
   257  	return nil
   258  }