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 }