package genericresource import ( "fmt" "github.com/docker/swarmkit/api" ) // Claim assigns GenericResources to a task by taking them from the // node's GenericResource list and storing them in the task's available list func Claim(nodeAvailableResources, taskAssigned *[]*api.GenericResource, taskReservations []*api.GenericResource) error { var resSelected []*api.GenericResource for _, res := range taskReservations { tr := res.GetDiscreteResourceSpec() if tr == nil { return fmt.Errorf("task should only hold Discrete type") } // Select the resources nrs, err := selectNodeResources(*nodeAvailableResources, tr) if err != nil { return err } resSelected = append(resSelected, nrs...) } ClaimResources(nodeAvailableResources, taskAssigned, resSelected) return nil } // ClaimResources adds the specified resources to the task's list // and removes them from the node's generic resource list func ClaimResources(nodeAvailableResources, taskAssigned *[]*api.GenericResource, resSelected []*api.GenericResource) { *taskAssigned = append(*taskAssigned, resSelected...) ConsumeNodeResources(nodeAvailableResources, resSelected) } func selectNodeResources(nodeRes []*api.GenericResource, tr *api.DiscreteGenericResource) ([]*api.GenericResource, error) { var nrs []*api.GenericResource for _, res := range nodeRes { if Kind(res) != tr.Kind { continue } switch nr := res.Resource.(type) { case *api.GenericResource_DiscreteResourceSpec: if nr.DiscreteResourceSpec.Value >= tr.Value && tr.Value != 0 { nrs = append(nrs, NewDiscrete(tr.Kind, tr.Value)) } return nrs, nil case *api.GenericResource_NamedResourceSpec: nrs = append(nrs, res.Copy()) if int64(len(nrs)) == tr.Value { return nrs, nil } } } if len(nrs) == 0 { return nil, fmt.Errorf("not enough resources available for task reservations: %+v", tr) } return nrs, nil } // Reclaim adds the resources taken by the task to the node's store func Reclaim(nodeAvailableResources *[]*api.GenericResource, taskAssigned, nodeRes []*api.GenericResource) error { err := reclaimResources(nodeAvailableResources, taskAssigned) if err != nil { return err } sanitize(nodeRes, nodeAvailableResources) return nil } func reclaimResources(nodeAvailableResources *[]*api.GenericResource, taskAssigned []*api.GenericResource) error { // The node could have been updated if nodeAvailableResources == nil { return fmt.Errorf("node no longer has any resources") } for _, res := range taskAssigned { switch tr := res.Resource.(type) { case *api.GenericResource_DiscreteResourceSpec: nrs := GetResource(tr.DiscreteResourceSpec.Kind, *nodeAvailableResources) // If the resource went down to 0 it's no longer in the // available list if len(nrs) == 0 { *nodeAvailableResources = append(*nodeAvailableResources, res.Copy()) } if len(nrs) != 1 { continue // Type change } nr := nrs[0].GetDiscreteResourceSpec() if nr == nil { continue // Type change } nr.Value += tr.DiscreteResourceSpec.Value case *api.GenericResource_NamedResourceSpec: *nodeAvailableResources = append(*nodeAvailableResources, res.Copy()) } } return nil } // sanitize checks that nodeAvailableResources does not add resources unknown // to the nodeSpec (nodeRes) or goes over the integer bound specified // by the spec. // Note this is because the user is able to update a node's resources func sanitize(nodeRes []*api.GenericResource, nodeAvailableResources *[]*api.GenericResource) { // - We add the sanitized resources at the end, after // having removed the elements from the list // - When a set changes to a Discrete we also need // to make sure that we don't add the Discrete multiple // time hence, the need of a map to remember that var sanitized []*api.GenericResource kindSanitized := make(map[string]struct{}) w := 0 for _, na := range *nodeAvailableResources { ok, nrs := sanitizeResource(nodeRes, na) if !ok { if _, ok = kindSanitized[Kind(na)]; ok { continue } kindSanitized[Kind(na)] = struct{}{} sanitized = append(sanitized, nrs...) continue } (*nodeAvailableResources)[w] = na w++ } *nodeAvailableResources = (*nodeAvailableResources)[:w] *nodeAvailableResources = append(*nodeAvailableResources, sanitized...) } // Returns true if the element is in nodeRes and "sane" // Returns false if the element isn't in nodeRes and "sane" and the element(s) that should be replacing it func sanitizeResource(nodeRes []*api.GenericResource, res *api.GenericResource) (ok bool, nrs []*api.GenericResource) { switch na := res.Resource.(type) { case *api.GenericResource_DiscreteResourceSpec: nrs := GetResource(na.DiscreteResourceSpec.Kind, nodeRes) // Type change or removed: reset if len(nrs) != 1 { return false, nrs } // Type change: reset nr := nrs[0].GetDiscreteResourceSpec() if nr == nil { return false, nrs } // Amount change: reset if na.DiscreteResourceSpec.Value > nr.Value { return false, nrs } case *api.GenericResource_NamedResourceSpec: nrs := GetResource(na.NamedResourceSpec.Kind, nodeRes) // Type change if len(nrs) == 0 { return false, nrs } for _, nr := range nrs { // Type change: reset if nr.GetDiscreteResourceSpec() != nil { return false, nrs } if na.NamedResourceSpec.Value == nr.GetNamedResourceSpec().Value { return true, nil } } // Removed return false, nil } return true, nil }