github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/host/host.go (about)

     1  package host
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/evergreen-ci/evergreen"
     8  	"github.com/evergreen-ci/evergreen/db"
     9  	"github.com/evergreen-ci/evergreen/model/distro"
    10  	"github.com/evergreen-ci/evergreen/model/event"
    11  	"github.com/evergreen-ci/evergreen/util"
    12  	"github.com/mongodb/grip"
    13  	"github.com/pkg/errors"
    14  	"gopkg.in/mgo.v2"
    15  	"gopkg.in/mgo.v2/bson"
    16  )
    17  
    18  type Host struct {
    19  	Id       string        `bson:"_id" json:"id"`
    20  	Host     string        `bson:"host_id" json:"host"`
    21  	User     string        `bson:"user" json:"user"`
    22  	Secret   string        `bson:"secret" json:"secret"`
    23  	Tag      string        `bson:"tag" json:"tag"`
    24  	Distro   distro.Distro `bson:"distro" json:"distro"`
    25  	Provider string        `bson:"host_type" json:"host_type"`
    26  
    27  	// true if the host has been set up properly
    28  	Provisioned bool `bson:"provisioned" json:"provisioned"`
    29  
    30  	ProvisionOptions *ProvisionOptions `bson:"provision_options,omitempty" json:"provision_options,omitempty"`
    31  
    32  	// the task that is currently running on the host
    33  	RunningTask string `bson:"running_task,omitempty" json:"running_task,omitempty"`
    34  
    35  	// the pid of the task that is currently running on the host
    36  	Pid string `bson:"pid" json:"pid"`
    37  
    38  	// duplicate of the DispatchTime field in the above task
    39  	TaskDispatchTime time.Time `bson:"task_dispatch_time" json:"task_dispatch_time"`
    40  	ExpirationTime   time.Time `bson:"expiration_time,omitempty" json:"expiration_time"`
    41  	CreationTime     time.Time `bson:"creation_time" json:"creation_time"`
    42  	TerminationTime  time.Time `bson:"termination_time" json:"termination_time"`
    43  
    44  	LastTaskCompletedTime time.Time `bson:"last_task_completed_time" json:"last_task_completed_time"`
    45  	LastTaskCompleted     string    `bson:"last_task" json:"last_task"`
    46  	LastCommunicationTime time.Time `bson:"last_communication" json:"last_communication"`
    47  
    48  	Status    string `bson:"status" json:"status"`
    49  	StartedBy string `bson:"started_by" json:"started_by"`
    50  	// True if this host was created manually by a user (i.e. with spawnhost)
    51  	UserHost      bool   `bson:"user_host" json:"user_host"`
    52  	AgentRevision string `bson:"agent_revision" json:"agent_revision"`
    53  	// for ec2 dynamic hosts, the instance type requested
    54  	InstanceType string `bson:"instance_type" json:"instance_type,omitempty"`
    55  	// stores information on expiration notifications for spawn hosts
    56  	Notifications map[string]bool `bson:"notifications,omitempty" json:"notifications,omitempty"`
    57  
    58  	// stores userdata that was placed on the host at spawn time
    59  	UserData string `bson:"userdata" json:"userdata,omitempty"`
    60  
    61  	// the last time that the host's reachability was checked
    62  	LastReachabilityCheck time.Time `bson:"last_reachability_check" json:"last_reachability_check"`
    63  
    64  	// if set, the time at which the host first became unreachable
    65  	UnreachableSince time.Time `bson:"unreachable_since,omitempty" json:"unreachable_since"`
    66  }
    67  
    68  // ProvisionOptions is struct containing options about how a new host should be set up.
    69  type ProvisionOptions struct {
    70  	// LoadCLI indicates (if set) that while provisioning the host, the CLI binary should
    71  	// be placed onto the host after startup.
    72  	LoadCLI bool `bson:"load_cli" json:"load_cli"`
    73  
    74  	// TaskId if non-empty will trigger the CLI tool to fetch source and artifacts for the given task.
    75  	// Ignored if LoadCLI is false.
    76  	TaskId string `bson:"task_id" json:"task_id"`
    77  
    78  	// Owner is the user associated with the host used to populate any necessary metadata.
    79  	OwnerId string `bson:"owner_id" json:"owner_id"`
    80  }
    81  
    82  const (
    83  	MaxLCTInterval = time.Minute * 10
    84  )
    85  
    86  // IdleTime returns how long has this host been idle
    87  func (h *Host) IdleTime() time.Duration {
    88  
    89  	// if the host is currently running a task, it is not idle
    90  	if h.RunningTask != "" {
    91  		return time.Duration(0)
    92  	}
    93  
    94  	// if the host has run a task before, then the idle time is just the time
    95  	// passed since the last task finished
    96  	if h.LastTaskCompleted != "" {
    97  		return time.Since(h.LastTaskCompletedTime)
    98  	}
    99  
   100  	// if the host has not run a task before, the idle time is just
   101  	// how long is has been since the host was created
   102  	return time.Since(h.CreationTime)
   103  }
   104  
   105  func (h *Host) SetStatus(status string) error {
   106  	if h.Status == evergreen.HostTerminated {
   107  		msg := fmt.Sprintf("Refusing to mark host %v as"+
   108  			" %v because it is already terminated", h.Id, status)
   109  		grip.Warning(msg)
   110  		return errors.New(msg)
   111  	}
   112  
   113  	event.LogHostStatusChanged(h.Id, h.Status, status)
   114  
   115  	h.Status = status
   116  	return UpdateOne(
   117  		bson.M{
   118  			IdKey: h.Id,
   119  		},
   120  		bson.M{
   121  			"$set": bson.M{
   122  				StatusKey: status,
   123  			},
   124  		},
   125  	)
   126  }
   127  
   128  // SetInitializing marks the host as initializing. Only allow this
   129  // if the host is uninitialized.
   130  func (h *Host) SetInitializing() error {
   131  	return UpdateOne(
   132  		bson.M{
   133  			IdKey:     h.Id,
   134  			StatusKey: evergreen.HostUninitialized,
   135  		},
   136  		bson.M{
   137  			"$set": bson.M{
   138  				StatusKey: evergreen.HostInitializing,
   139  			},
   140  		},
   141  	)
   142  }
   143  
   144  func (h *Host) SetDecommissioned() error {
   145  	return h.SetStatus(evergreen.HostDecommissioned)
   146  }
   147  
   148  func (h *Host) SetUninitialized() error {
   149  	return h.SetStatus(evergreen.HostUninitialized)
   150  }
   151  
   152  func (h *Host) SetRunning() error {
   153  	return h.SetStatus(evergreen.HostRunning)
   154  }
   155  
   156  func (h *Host) SetTerminated() error {
   157  	return h.SetStatus(evergreen.HostTerminated)
   158  }
   159  
   160  func (h *Host) SetUnreachable() error {
   161  	return h.SetStatus(evergreen.HostUnreachable)
   162  }
   163  
   164  func (h *Host) SetUnprovisioned() error {
   165  	return UpdateOne(
   166  		bson.M{
   167  			IdKey:     h.Id,
   168  			StatusKey: evergreen.HostInitializing,
   169  		},
   170  		bson.M{
   171  			"$set": bson.M{
   172  				StatusKey: evergreen.HostProvisionFailed,
   173  			},
   174  		},
   175  	)
   176  }
   177  
   178  func (h *Host) SetQuarantined() error {
   179  	return h.SetStatus(evergreen.HostQuarantined)
   180  }
   181  
   182  // CreateSecret generates a host secret and updates the host both locally
   183  // and in the database.
   184  func (h *Host) CreateSecret() error {
   185  	secret := util.RandomString()
   186  	err := UpdateOne(
   187  		bson.M{IdKey: h.Id},
   188  		bson.M{"$set": bson.M{SecretKey: secret}},
   189  	)
   190  	if err != nil {
   191  		return err
   192  	}
   193  	h.Secret = secret
   194  	return nil
   195  }
   196  
   197  // UpdateLastCommunicated sets the host's last communication time to the current time.
   198  func (h *Host) UpdateLastCommunicated() error {
   199  	now := time.Now()
   200  	err := UpdateOne(
   201  		bson.M{IdKey: h.Id},
   202  		bson.M{"$set": bson.M{LastCommunicationTimeKey: now}},
   203  	)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	h.LastCommunicationTime = now
   208  	return nil
   209  }
   210  
   211  // ResetLastCommunicated sets the LastCommunicationTime to be zero.
   212  func (h *Host) ResetLastCommunicated() error {
   213  	err := UpdateOne(
   214  		bson.M{IdKey: h.Id},
   215  		bson.M{"$set": bson.M{LastCommunicationTimeKey: time.Unix(0, 0)}})
   216  	if err != nil {
   217  		return err
   218  	}
   219  	h.LastCommunicationTime = time.Unix(0, 0)
   220  	return nil
   221  }
   222  
   223  func (h *Host) Terminate() error {
   224  	err := h.SetTerminated()
   225  	if err != nil {
   226  		return err
   227  	}
   228  	h.TerminationTime = time.Now()
   229  	return UpdateOne(
   230  		bson.M{
   231  			IdKey: h.Id,
   232  		},
   233  		bson.M{
   234  			"$set": bson.M{
   235  				TerminationTimeKey: h.TerminationTime,
   236  			},
   237  		},
   238  	)
   239  }
   240  
   241  // SetDNSName updates the DNS name for a given host once
   242  func (h *Host) SetDNSName(dnsName string) error {
   243  	err := UpdateOne(
   244  		bson.M{
   245  			IdKey:  h.Id,
   246  			DNSKey: "",
   247  		},
   248  		bson.M{
   249  			"$set": bson.M{
   250  				DNSKey: dnsName,
   251  			},
   252  		},
   253  	)
   254  	if err == nil {
   255  		h.Host = dnsName
   256  		event.LogHostDNSNameSet(h.Id, dnsName)
   257  	}
   258  	if err == mgo.ErrNotFound {
   259  		return nil
   260  	}
   261  	return err
   262  }
   263  
   264  func (h *Host) MarkAsProvisioned() error {
   265  	event.LogHostProvisioned(h.Id)
   266  	h.Status = evergreen.HostRunning
   267  	h.Provisioned = true
   268  	return UpdateOne(
   269  		bson.M{
   270  			IdKey: h.Id,
   271  		},
   272  		bson.M{
   273  			"$set": bson.M{
   274  				StatusKey:      evergreen.HostRunning,
   275  				ProvisionedKey: true,
   276  			},
   277  		},
   278  	)
   279  }
   280  
   281  // ClearRunningTask unsets the running task key on the host and updates the last task
   282  // completed fields.
   283  func (host *Host) ClearRunningTask(prevTaskId string, finishTime time.Time) error {
   284  	host.LastTaskCompleted = prevTaskId
   285  	host.LastTaskCompletedTime = finishTime
   286  	host.RunningTask = ""
   287  	event.LogHostRunningTaskCleared(host.Id, prevTaskId)
   288  	return UpdateOne(
   289  		bson.M{
   290  			IdKey: host.Id,
   291  		},
   292  		bson.M{
   293  			"$set": bson.M{
   294  				LTCKey:     prevTaskId,
   295  				LTCTimeKey: finishTime,
   296  			},
   297  			"$unset": bson.M{
   298  				RunningTaskKey: 1,
   299  			},
   300  		})
   301  
   302  }
   303  
   304  // UpdateRunningTask takes two id strings - an old task and a new one - finds
   305  // the host running the task with Id, 'prevTaskId' and updates its running task
   306  // to 'newTaskId'; also setting the completion time of 'prevTaskId'
   307  // Returns true for success and error if it exists
   308  func (host *Host) UpdateRunningTask(prevTaskId, newTaskId string,
   309  	finishTime time.Time) (bool, error) {
   310  
   311  	// we should never be calling update running task with an empty new task id.
   312  	if newTaskId == "" {
   313  		return false, fmt.Errorf("cannot set a running task id to be an empty string")
   314  	}
   315  
   316  	selector := bson.M{
   317  		IdKey: host.Id,
   318  	}
   319  
   320  	update := bson.M{
   321  		"$set": bson.M{
   322  			RunningTaskKey: newTaskId,
   323  			LTCKey:         prevTaskId,
   324  			LTCTimeKey:     finishTime,
   325  			PidKey:         "",
   326  		},
   327  	}
   328  
   329  	err := UpdateOne(selector, update)
   330  	if err != nil {
   331  		// if its a duplicate key error, don't log the error.
   332  		if mgo.IsDup(err) {
   333  			return false, nil
   334  		}
   335  		return false, err
   336  	}
   337  	event.LogHostRunningTaskSet(host.Id, newTaskId)
   338  
   339  	return true, nil
   340  }
   341  
   342  // SetAgentRevision sets the updated agent revision for the host
   343  func (h *Host) SetAgentRevision(agentRevision string) error {
   344  	err := UpdateOne(bson.M{IdKey: h.Id},
   345  		bson.M{"$set": bson.M{AgentRevisionKey: agentRevision}})
   346  	if err != nil {
   347  		return err
   348  	}
   349  	h.AgentRevision = agentRevision
   350  	return nil
   351  }
   352  
   353  // SetExpirationTime updates the expiration time of a spawn host
   354  func (h *Host) SetExpirationTime(expirationTime time.Time) error {
   355  	// update the in-memory host, then the database
   356  	h.ExpirationTime = expirationTime
   357  	h.Notifications = make(map[string]bool)
   358  	return UpdateOne(
   359  		bson.M{
   360  			IdKey: h.Id,
   361  		},
   362  		bson.M{
   363  			"$set": bson.M{
   364  				ExpirationTimeKey: expirationTime,
   365  			},
   366  			"$unset": bson.M{
   367  				NotificationsKey: 1,
   368  			},
   369  		},
   370  	)
   371  }
   372  
   373  // SetUserData updates the userdata field of a spawn host
   374  func (h *Host) SetUserData(userData string) error {
   375  	// update the in-memory host, then the database
   376  	h.UserData = userData
   377  	return UpdateOne(
   378  		bson.M{
   379  			IdKey: h.Id,
   380  		},
   381  		bson.M{
   382  			"$set": bson.M{
   383  				UserDataKey: userData,
   384  			},
   385  		},
   386  	)
   387  }
   388  
   389  // SetExpirationNotification updates the notification time for a spawn host
   390  func (h *Host) SetExpirationNotification(thresholdKey string) error {
   391  	// update the in-memory host, then the database
   392  	if h.Notifications == nil {
   393  		h.Notifications = make(map[string]bool)
   394  	}
   395  	h.Notifications[thresholdKey] = true
   396  	return UpdateOne(
   397  		bson.M{
   398  			IdKey: h.Id,
   399  		},
   400  		bson.M{
   401  			"$set": bson.M{
   402  				NotificationsKey: h.Notifications,
   403  			},
   404  		},
   405  	)
   406  }
   407  
   408  func (h *Host) SetTaskPid(pid string) error {
   409  	event.LogHostTaskPidSet(h.Id, pid)
   410  	return UpdateOne(
   411  		bson.M{
   412  			IdKey: h.Id,
   413  		},
   414  		bson.M{
   415  			"$set": bson.M{
   416  				PidKey: pid,
   417  			},
   418  		},
   419  	)
   420  }
   421  
   422  // UpdateReachability sets a host as either running or unreachable,
   423  // and updates the timestamp of the host's last reachability check.
   424  // If the host is being set to unreachable, the "unreachable since" field
   425  // is also set to the current time if it is unset.
   426  func (h *Host) UpdateReachability(reachable bool) error {
   427  	status := evergreen.HostRunning
   428  	setUpdate := bson.M{
   429  		StatusKey:                status,
   430  		LastReachabilityCheckKey: time.Now(),
   431  	}
   432  
   433  	update := bson.M{}
   434  	if !reachable {
   435  		status = evergreen.HostUnreachable
   436  		setUpdate[StatusKey] = status
   437  
   438  		// If the host is being switched to unreachable for the first time, then
   439  		// "unreachable since" will be unset, so we set it to the current time.
   440  		if h.UnreachableSince.Equal(util.ZeroTime) || h.UnreachableSince.Before(util.ZeroTime) {
   441  			now := time.Now()
   442  			setUpdate[UnreachableSinceKey] = now
   443  			h.UnreachableSince = now
   444  		}
   445  	} else {
   446  		// host is reachable, so unset the unreachable_since field
   447  		update["$unset"] = bson.M{UnreachableSinceKey: 1}
   448  		h.UnreachableSince = util.ZeroTime
   449  	}
   450  	update["$set"] = setUpdate
   451  
   452  	event.LogHostStatusChanged(h.Id, h.Status, status)
   453  
   454  	h.Status = status
   455  
   456  	return UpdateOne(bson.M{IdKey: h.Id}, update)
   457  }
   458  
   459  func (h *Host) Upsert() (*mgo.ChangeInfo, error) {
   460  
   461  	return UpsertOne(
   462  		bson.M{
   463  			IdKey: h.Id,
   464  		},
   465  		bson.M{
   466  			"$set": bson.M{
   467  				DNSKey:         h.Host,
   468  				UserKey:        h.User,
   469  				DistroKey:      h.Distro,
   470  				ProvisionedKey: h.Provisioned,
   471  				StartedByKey:   h.StartedBy,
   472  				ProviderKey:    h.Provider,
   473  			},
   474  			"$setOnInsert": bson.M{
   475  				StatusKey:     h.Status,
   476  				CreateTimeKey: h.CreationTime,
   477  			},
   478  		},
   479  	)
   480  }
   481  
   482  func (h *Host) Insert() error {
   483  	event.LogHostCreated(h.Id)
   484  	return db.Insert(Collection, h)
   485  }
   486  
   487  func (h *Host) Remove() error {
   488  	return db.Remove(
   489  		Collection,
   490  		bson.M{
   491  			IdKey: h.Id,
   492  		},
   493  	)
   494  }
   495  
   496  func DecommissionHostsWithDistroId(distroId string) error {
   497  	err := UpdateAll(
   498  		ByDistroId(distroId),
   499  		bson.M{
   500  			"$set": bson.M{
   501  				StatusKey: evergreen.HostDecommissioned,
   502  			},
   503  		},
   504  	)
   505  	return err
   506  }