2016-06-07 14:28:28 -07:00
package scheduler
2016-10-26 06:35:48 -07:00
import (
"time"
"github.com/docker/swarmkit/api"
2017-06-12 10:27:11 -07:00
"github.com/docker/swarmkit/api/genericresource"
2016-10-26 06:35:48 -07:00
"github.com/docker/swarmkit/log"
"golang.org/x/net/context"
)
2016-06-07 14:28:28 -07:00
2017-05-11 15:18:12 -07:00
// hostPortSpec specifies a used host port.
type hostPortSpec struct {
protocol api . PortConfig_Protocol
publishedPort uint32
}
2017-07-11 12:22:34 -07:00
// versionedService defines a tuple that contains a service ID and a spec
// version, so that failures can be tracked per spec version. Note that if the
// task predates spec versioning, specVersion will contain the zero value, and
// this will still work correctly.
type versionedService struct {
serviceID string
specVersion api . Version
}
2016-06-07 14:28:28 -07:00
// NodeInfo contains a node and some additional metadata.
type NodeInfo struct {
* api . Node
2017-02-27 11:51:00 -08:00
Tasks map [ string ] * api . Task
ActiveTasksCount int
ActiveTasksCountByService map [ string ] int
2017-06-12 10:27:11 -07:00
AvailableResources * api . Resources
2017-05-11 15:18:12 -07:00
usedHostPorts map [ hostPortSpec ] struct { }
2016-10-26 06:35:48 -07:00
2017-07-11 12:22:34 -07:00
// recentFailures is a map from service ID/version to the timestamps of
// the most recent failures the node has experienced from replicas of
// that service.
recentFailures map [ versionedService ] [ ] time . Time
// lastCleanup is the last time recentFailures was cleaned up. This is
// done periodically to avoid recentFailures growing without any limit.
lastCleanup time . Time
2016-06-07 14:28:28 -07:00
}
2016-06-15 22:41:30 -07:00
func newNodeInfo ( n * api . Node , tasks map [ string ] * api . Task , availableResources api . Resources ) NodeInfo {
2016-06-07 14:28:28 -07:00
nodeInfo := NodeInfo {
2016-09-13 09:28:01 -07:00
Node : n ,
Tasks : make ( map [ string ] * api . Task ) ,
2017-02-27 11:51:00 -08:00
ActiveTasksCountByService : make ( map [ string ] int ) ,
2017-06-12 10:27:11 -07:00
AvailableResources : availableResources . Copy ( ) ,
2017-05-11 15:18:12 -07:00
usedHostPorts : make ( map [ hostPortSpec ] struct { } ) ,
2017-07-11 12:22:34 -07:00
recentFailures : make ( map [ versionedService ] [ ] time . Time ) ,
lastCleanup : time . Now ( ) ,
2016-06-07 14:28:28 -07:00
}
for _ , t := range tasks {
nodeInfo . addTask ( t )
}
2017-06-12 10:27:11 -07:00
2016-06-07 14:28:28 -07:00
return nodeInfo
}
2017-01-19 17:18:22 -08:00
// removeTask removes a task from nodeInfo if it's tracked there, and returns true
2016-09-13 09:28:01 -07:00
// if nodeInfo was modified.
2016-06-07 14:28:28 -07:00
func ( nodeInfo * NodeInfo ) removeTask ( t * api . Task ) bool {
2016-09-13 09:28:01 -07:00
oldTask , ok := nodeInfo . Tasks [ t . ID ]
if ! ok {
2016-06-07 14:28:28 -07:00
return false
}
delete ( nodeInfo . Tasks , t . ID )
2017-02-27 11:51:00 -08:00
if oldTask . DesiredState <= api . TaskStateRunning {
nodeInfo . ActiveTasksCount --
nodeInfo . ActiveTasksCountByService [ t . ServiceID ] --
2016-09-13 09:28:01 -07:00
}
2017-06-15 11:11:48 -07:00
if t . Endpoint != nil {
for _ , port := range t . Endpoint . Ports {
if port . PublishMode == api . PublishModeHost && port . PublishedPort != 0 {
portSpec := hostPortSpec { protocol : port . Protocol , publishedPort : port . PublishedPort }
delete ( nodeInfo . usedHostPorts , portSpec )
}
}
}
2016-06-15 22:41:30 -07:00
reservations := taskReservations ( t . Spec )
2017-06-12 10:27:11 -07:00
resources := nodeInfo . AvailableResources
resources . MemoryBytes += reservations . MemoryBytes
resources . NanoCPUs += reservations . NanoCPUs
if nodeInfo . Description == nil || nodeInfo . Description . Resources == nil ||
nodeInfo . Description . Resources . Generic == nil {
return true
}
taskAssigned := t . AssignedGenericResources
nodeAvailableResources := & resources . Generic
nodeRes := nodeInfo . Description . Resources . Generic
genericresource . Reclaim ( nodeAvailableResources , taskAssigned , nodeRes )
2016-06-07 14:28:28 -07:00
return true
}
2016-09-13 09:28:01 -07:00
// addTask adds or updates a task on nodeInfo, and returns true if nodeInfo was
// modified.
2016-06-07 14:28:28 -07:00
func ( nodeInfo * NodeInfo ) addTask ( t * api . Task ) bool {
2016-09-13 09:28:01 -07:00
oldTask , ok := nodeInfo . Tasks [ t . ID ]
if ok {
2017-02-27 11:51:00 -08:00
if t . DesiredState <= api . TaskStateRunning && oldTask . DesiredState > api . TaskStateRunning {
2016-09-13 09:28:01 -07:00
nodeInfo . Tasks [ t . ID ] = t
2017-02-27 11:51:00 -08:00
nodeInfo . ActiveTasksCount ++
nodeInfo . ActiveTasksCountByService [ t . ServiceID ] ++
2016-09-13 09:28:01 -07:00
return true
2017-02-27 11:51:00 -08:00
} else if t . DesiredState > api . TaskStateRunning && oldTask . DesiredState <= api . TaskStateRunning {
2016-09-13 09:28:01 -07:00
nodeInfo . Tasks [ t . ID ] = t
2017-02-27 11:51:00 -08:00
nodeInfo . ActiveTasksCount --
nodeInfo . ActiveTasksCountByService [ t . ServiceID ] --
2016-09-13 09:28:01 -07:00
return true
}
return false
}
nodeInfo . Tasks [ t . ID ] = t
2017-06-12 10:27:11 -07:00
2016-09-13 09:28:01 -07:00
reservations := taskReservations ( t . Spec )
2017-06-12 10:27:11 -07:00
resources := nodeInfo . AvailableResources
resources . MemoryBytes -= reservations . MemoryBytes
resources . NanoCPUs -= reservations . NanoCPUs
// minimum size required
t . AssignedGenericResources = make ( [ ] * api . GenericResource , 0 , len ( resources . Generic ) )
taskAssigned := & t . AssignedGenericResources
genericresource . Claim ( & resources . Generic , taskAssigned , reservations . Generic )
2016-09-13 09:28:01 -07:00
2017-05-11 15:18:12 -07:00
if t . Endpoint != nil {
for _ , port := range t . Endpoint . Ports {
if port . PublishMode == api . PublishModeHost && port . PublishedPort != 0 {
portSpec := hostPortSpec { protocol : port . Protocol , publishedPort : port . PublishedPort }
nodeInfo . usedHostPorts [ portSpec ] = struct { } { }
}
}
}
2017-02-27 11:51:00 -08:00
if t . DesiredState <= api . TaskStateRunning {
nodeInfo . ActiveTasksCount ++
nodeInfo . ActiveTasksCountByService [ t . ServiceID ] ++
2016-06-07 14:28:28 -07:00
}
2016-09-13 09:28:01 -07:00
return true
2016-06-07 14:28:28 -07:00
}
func taskReservations ( spec api . TaskSpec ) ( reservations api . Resources ) {
if spec . Resources != nil && spec . Resources . Reservations != nil {
reservations = * spec . Resources . Reservations
}
return
}
2016-10-26 06:35:48 -07:00
2017-07-11 12:22:34 -07:00
func ( nodeInfo * NodeInfo ) cleanupFailures ( now time . Time ) {
entriesLoop :
for key , failuresEntry := range nodeInfo . recentFailures {
for _ , timestamp := range failuresEntry {
if now . Sub ( timestamp ) < monitorFailures {
continue entriesLoop
}
}
delete ( nodeInfo . recentFailures , key )
}
nodeInfo . lastCleanup = now
}
2016-10-26 06:35:48 -07:00
// taskFailed records a task failure from a given service.
2017-07-11 12:22:34 -07:00
func ( nodeInfo * NodeInfo ) taskFailed ( ctx context . Context , t * api . Task ) {
2016-10-26 06:35:48 -07:00
expired := 0
now := time . Now ( )
2017-07-11 12:22:34 -07:00
if now . Sub ( nodeInfo . lastCleanup ) >= monitorFailures {
nodeInfo . cleanupFailures ( now )
}
versionedService := versionedService { serviceID : t . ServiceID }
if t . SpecVersion != nil {
versionedService . specVersion = * t . SpecVersion
}
for _ , timestamp := range nodeInfo . recentFailures [ versionedService ] {
2016-10-26 06:35:48 -07:00
if now . Sub ( timestamp ) < monitorFailures {
break
}
expired ++
}
2017-07-11 12:22:34 -07:00
if len ( nodeInfo . recentFailures [ versionedService ] ) - expired == maxFailures - 1 {
log . G ( ctx ) . Warnf ( "underweighting node %s for service %s because it experienced %d failures or rejections within %s" , nodeInfo . ID , t . ServiceID , maxFailures , monitorFailures . String ( ) )
2016-10-26 06:35:48 -07:00
}
2017-07-11 12:22:34 -07:00
nodeInfo . recentFailures [ versionedService ] = append ( nodeInfo . recentFailures [ versionedService ] [ expired : ] , now )
2016-10-26 06:35:48 -07:00
}
// countRecentFailures returns the number of times the service has failed on
// this node within the lookback window monitorFailures.
2017-07-11 12:22:34 -07:00
func ( nodeInfo * NodeInfo ) countRecentFailures ( now time . Time , t * api . Task ) int {
versionedService := versionedService { serviceID : t . ServiceID }
if t . SpecVersion != nil {
versionedService . specVersion = * t . SpecVersion
}
recentFailureCount := len ( nodeInfo . recentFailures [ versionedService ] )
2016-10-26 06:35:48 -07:00
for i := recentFailureCount - 1 ; i >= 0 ; i -- {
2017-07-11 12:22:34 -07:00
if now . Sub ( nodeInfo . recentFailures [ versionedService ] [ i ] ) > monitorFailures {
2016-10-26 06:35:48 -07:00
recentFailureCount -= i + 1
break
}
}
return recentFailureCount
}