k3s/vendor/github.com/mesos/mesos-go/api/v1/lib/resources.go

1146 lines
34 KiB
Go

package mesos
import (
"bytes"
"fmt"
"strconv"
"github.com/gogo/protobuf/proto"
"github.com/mesos/mesos-go/api/v1/lib/roles"
)
const DefaultRole = "*"
type (
Resources []Resource
resourceErrorType int
resourceError struct {
errorType resourceErrorType
reason string
spec Resource
}
)
const (
resourceErrorTypeIllegalName resourceErrorType = iota
resourceErrorTypeIllegalType
resourceErrorTypeUnsupportedType
resourceErrorTypeIllegalScalar
resourceErrorTypeIllegalRanges
resourceErrorTypeIllegalSet
resourceErrorTypeIllegalDisk
resourceErrorTypeIllegalReservation
resourceErrorTypeIllegalShare
noReason = "" // make error generation code more readable
)
var (
resourceErrorMessages = map[resourceErrorType]string{
resourceErrorTypeIllegalName: "missing or illegal resource name",
resourceErrorTypeIllegalType: "missing or illegal resource type",
resourceErrorTypeUnsupportedType: "unsupported resource type",
resourceErrorTypeIllegalScalar: "illegal scalar resource",
resourceErrorTypeIllegalRanges: "illegal ranges resource",
resourceErrorTypeIllegalSet: "illegal set resource",
resourceErrorTypeIllegalDisk: "illegal disk resource",
resourceErrorTypeIllegalReservation: "illegal resource reservation",
resourceErrorTypeIllegalShare: "illegal shared resource",
}
)
func (t resourceErrorType) Generate(reason string) error {
msg := resourceErrorMessages[t]
if reason != noReason {
if msg != "" {
msg += ": " + reason
} else {
msg = reason
}
}
return &resourceError{errorType: t, reason: msg}
}
func (err *resourceError) Reason() string { return err.reason }
func (err *resourceError) Resource() Resource { return err.spec }
func (err *resourceError) WithResource(r Resource) { err.spec = r }
func (err *resourceError) Error() string {
// TODO(jdef) include additional context here? (type, resource)
if err.reason != "" {
return "resource error: " + err.reason
}
return "resource error"
}
func IsResourceError(err error) (ok bool) {
_, ok = err.(*resourceError)
return
}
func (r *Resource_ReservationInfo) Assign() func(interface{}) {
return func(v interface{}) {
type reserver interface {
WithReservation(*Resource_ReservationInfo)
}
if ri, ok := v.(reserver); ok {
ri.WithReservation(r)
}
}
}
func (resources Resources) Clone() Resources {
if resources == nil {
return nil
}
clone := make(Resources, 0, len(resources))
for i := range resources {
rr := proto.Clone(&resources[i]).(*Resource)
clone = append(clone, *rr)
}
return clone
}
// Minus calculates and returns the result of `resources - that` without modifying either
// the receiving `resources` or `that`.
func (resources Resources) Minus(that ...Resource) Resources {
x := resources.Clone()
return x.Subtract(that...)
}
// Subtract subtracts `that` from the receiving `resources` and returns the result (the modified
// `resources` receiver).
func (resources *Resources) Subtract(that ...Resource) (rs Resources) {
if resources != nil {
if len(that) > 0 {
x := make(Resources, len(that))
copy(x, that)
that = x
for i := range that {
resources.Subtract1(that[i])
}
}
rs = *resources
}
return
}
// Plus calculates and returns the result of `resources + that` without modifying either
// the receiving `resources` or `that`.
func (resources Resources) Plus(that ...Resource) Resources {
x := resources.Clone()
return x.Add(that...)
}
// Add adds `that` to the receiving `resources` and returns the result (the modified
// `resources` receiver).
func (resources *Resources) Add(that ...Resource) (rs Resources) {
if resources != nil {
rs = *resources
}
for i := range that {
rs = rs._add(that[i])
}
if resources != nil {
*resources = rs
}
return
}
// Add1 adds `that` to the receiving `resources` and returns the result (the modified
// `resources` receiver).
func (resources *Resources) Add1(that Resource) (rs Resources) {
if resources != nil {
rs = *resources
}
rs = rs._add(that)
if resources != nil {
*resources = rs
}
return
}
func (resources Resources) _add(that Resource) Resources {
if that.Validate() != nil || that.IsEmpty() {
return resources
}
for i := range resources {
r := &resources[i]
if r.Addable(that) {
r.Add(that)
return resources
}
}
// cannot be combined with an existing resource
r := proto.Clone(&that).(*Resource)
return append(resources, *r)
}
// Minus1 calculates and returns the result of `resources - that` without modifying either
// the receiving `resources` or `that`.
func (resources *Resources) Minus1(that Resource) Resources {
x := resources.Clone()
return x.Subtract1(that)
}
// Subtract1 subtracts `that` from the receiving `resources` and returns the result (the modified
// `resources` receiver).
func (resources *Resources) Subtract1(that Resource) Resources {
if resources == nil {
return nil
}
if that.Validate() == nil && !that.IsEmpty() {
for i := range *resources {
r := &(*resources)[i]
if r.Subtractable(that) {
r.Subtract(that)
// remove the resource if it becomes invalid or zero.
// need to do validation in order to strip negative scalar
// resource objects.
if r.Validate() != nil || r.IsEmpty() {
// delete resource at i, without leaking an uncollectable Resource
// a, a[len(a)-1] = append(a[:i], a[i+1:]...), nil
(*resources), (*resources)[len((*resources))-1] = append((*resources)[:i], (*resources)[i+1:]...), Resource{}
}
break
}
}
}
return *resources
}
// String returns a human-friendly representation of the resource collection using default formatting
// options (e.g. allocation-info is not rendered). For additional control over resource formatting see
// the Format func.
func (resources Resources) String() string {
return resources.Format()
}
type ResourcesFormatOptions struct {
ShowAllocated bool // ShowAllocated when true will not display resource allocation info
}
func (resources Resources) Format(options ...func(*ResourcesFormatOptions)) string {
if len(resources) == 0 {
return ""
}
var f ResourcesFormatOptions
for _, o := range options {
if o != nil {
o(&f)
}
}
// TODO(jdef) use a string.Builder once we can rely on a more modern golang version
buf := bytes.Buffer{}
for i := range resources {
if i > 0 {
buf.WriteString(";")
}
r := &resources[i]
buf.WriteString(r.Name)
if r.AllocationInfo != nil && f.ShowAllocated {
buf.WriteString("(allocated: ")
buf.WriteString(r.AllocationInfo.GetRole())
buf.WriteString(")")
}
if res := r.Reservations; len(res) > 0 || (r.Role != nil && *r.Role != "*") {
if len(res) == 0 {
res = make([]Resource_ReservationInfo, 0, 1)
if r.Reservation == nil {
res = append(res, Resource_ReservationInfo{
Type: Resource_ReservationInfo_STATIC.Enum(),
Role: r.Role,
})
} else {
res = append(res, *r.Reservation) // copy!
res[0].Type = Resource_ReservationInfo_DYNAMIC.Enum()
res[0].Role = r.Role
}
}
buf.WriteString("(reservations: [")
for j := range res {
if j > 0 {
buf.WriteString(",")
}
rr := &res[j]
buf.WriteString("(")
buf.WriteString(rr.GetType().String())
buf.WriteString(",")
buf.WriteString(rr.GetRole())
if rr.Principal != nil {
buf.WriteString(",")
buf.WriteString(*rr.Principal)
}
if rr.Labels != nil {
buf.WriteString(",{")
rr.GetLabels().writeTo(&buf)
buf.WriteString("}")
}
buf.WriteString(")")
}
buf.WriteString("])")
}
if d := r.GetDisk(); d != nil {
buf.WriteString("[")
if s := d.GetSource(); s != nil {
switch s.GetType() {
case Resource_DiskInfo_Source_BLOCK:
buf.WriteString("BLOCK")
if id, profile := s.GetID(), s.GetProfile(); id != "" || profile != "" {
buf.WriteByte('(')
buf.WriteString(id)
buf.WriteByte(',')
buf.WriteString(profile)
buf.WriteByte(')')
}
case Resource_DiskInfo_Source_RAW:
buf.WriteString("RAW")
if id, profile := s.GetID(), s.GetProfile(); id != "" || profile != "" {
buf.WriteByte('(')
buf.WriteString(id)
buf.WriteByte(',')
buf.WriteString(profile)
buf.WriteByte(')')
}
case Resource_DiskInfo_Source_PATH:
buf.WriteString("PATH")
if id, profile := s.GetID(), s.GetProfile(); id != "" || profile != "" {
buf.WriteByte('(')
buf.WriteString(id)
buf.WriteByte(',')
buf.WriteString(profile)
buf.WriteByte(')')
} else if root := s.GetPath().GetRoot(); root != "" {
buf.WriteByte(':')
buf.WriteString(root)
}
case Resource_DiskInfo_Source_MOUNT:
buf.WriteString("MOUNT")
if id, profile := s.GetID(), s.GetProfile(); id != "" || profile != "" {
buf.WriteByte('(')
buf.WriteString(id)
buf.WriteByte(',')
buf.WriteString(profile)
buf.WriteByte(')')
} else if root := s.GetMount().GetRoot(); root != "" {
buf.WriteByte(':')
buf.WriteString(root)
}
}
}
if p := d.GetPersistence(); p != nil {
if d.GetSource() != nil {
buf.WriteString(",")
}
buf.WriteString(p.GetID())
}
if v := d.GetVolume(); v != nil {
buf.WriteString(":")
vconfig := v.GetContainerPath()
if h := v.GetHostPath(); h != "" {
vconfig = h + ":" + vconfig
}
if m := v.Mode; m != nil {
switch *m {
case RO:
vconfig += ":ro"
case RW:
vconfig += ":rw"
default:
panic("unrecognized volume mode: " + m.String())
}
}
buf.WriteString(vconfig)
}
buf.WriteString("]")
}
if r.Revocable != nil {
buf.WriteString("{REV}")
}
if r.Shared != nil {
buf.WriteString("<SHARED>")
}
buf.WriteString(":")
switch r.GetType() {
case SCALAR:
buf.WriteString(strconv.FormatFloat(r.GetScalar().GetValue(), 'f', -1, 64))
case RANGES:
buf.WriteString("[")
ranges := Ranges(r.GetRanges().GetRange())
for j := range ranges {
if j > 0 {
buf.WriteString(",")
}
if b, e := ranges[j].Begin, ranges[j].End; b == e {
buf.WriteString(strconv.FormatUint(b, 10))
} else {
buf.WriteString(strconv.FormatUint(b, 10))
buf.WriteString("-")
buf.WriteString(strconv.FormatUint(e, 10))
}
}
buf.WriteString("]")
case SET:
buf.WriteString("{")
items := r.GetSet().GetItem()
for j := range items {
if j > 0 {
buf.WriteString(",")
}
buf.WriteString(items[j])
}
buf.WriteString("}")
}
}
return buf.String()
}
func (left *Resource) Validate() error {
if left.GetName() == "" {
return resourceErrorTypeIllegalName.Generate(noReason)
}
if _, ok := Value_Type_name[int32(left.GetType())]; !ok {
return resourceErrorTypeIllegalType.Generate(noReason)
}
switch left.GetType() {
case SCALAR:
if s := left.GetScalar(); s == nil || left.GetRanges() != nil || left.GetSet() != nil {
return resourceErrorTypeIllegalScalar.Generate(noReason)
} else if s.GetValue() < 0 {
return resourceErrorTypeIllegalScalar.Generate("value < 0")
}
case RANGES:
r := left.GetRanges()
if left.GetScalar() != nil || r == nil || left.GetSet() != nil {
return resourceErrorTypeIllegalRanges.Generate(noReason)
}
for i, rr := range r.GetRange() {
// ensure that ranges are not inverted
if rr.Begin > rr.End {
return resourceErrorTypeIllegalRanges.Generate("begin > end")
}
// ensure that ranges don't overlap (but not necessarily squashed)
for j := i + 1; j < len(r.GetRange()); j++ {
r2 := r.GetRange()[j]
if rr.Begin <= r2.Begin && r2.Begin <= rr.End {
return resourceErrorTypeIllegalRanges.Generate("overlapping ranges")
}
}
}
case SET:
s := left.GetSet()
if left.GetScalar() != nil || left.GetRanges() != nil || s == nil {
return resourceErrorTypeIllegalSet.Generate(noReason)
}
unique := make(map[string]struct{}, len(s.GetItem()))
for _, x := range s.GetItem() {
if _, found := unique[x]; found {
return resourceErrorTypeIllegalSet.Generate("duplicated elements")
}
unique[x] = struct{}{}
}
default:
return resourceErrorTypeUnsupportedType.Generate(noReason)
}
// check for disk resource
if disk := left.GetDisk(); disk != nil {
if left.GetName() != "disk" {
return resourceErrorTypeIllegalDisk.Generate("DiskInfo should not be set for \"" + left.GetName() + "\" resource")
}
if s := disk.GetSource(); s != nil {
switch s.GetType() {
case Resource_DiskInfo_Source_PATH,
Resource_DiskInfo_Source_MOUNT:
// these only contain optional members
case Resource_DiskInfo_Source_BLOCK,
Resource_DiskInfo_Source_RAW:
// TODO(jdef): update w/ validation once the format of BLOCK and RAW
// disks is known.
case Resource_DiskInfo_Source_UNKNOWN:
return resourceErrorTypeIllegalDisk.Generate(fmt.Sprintf("unsupported DiskInfo.Source.Type in %q", s))
}
}
}
if rs := left.GetReservations(); len(rs) == 0 {
// check for "pre-reservation-refinement" format
if _, err := roles.Parse(left.GetRole()); err != nil {
return resourceErrorTypeIllegalReservation.Generate(err.Error())
}
if r := left.GetReservation(); r != nil {
if r.Type != nil {
return resourceErrorTypeIllegalReservation.Generate(
"Resource.ReservationInfo.type must not be set for the Resource.reservation field")
}
if r.Role != nil {
return resourceErrorTypeIllegalReservation.Generate(
"Resource.ReservationInfo.role must not be set for the Resource.reservation field")
}
// check for invalid state of (role,reservation) pair
if left.GetRole() == "*" {
return resourceErrorTypeIllegalReservation.Generate("default role cannot be dynamically reserved")
}
}
} else {
// check for "post-reservation-refinement" format
for i := range rs {
r := &rs[i]
if r.Type == nil {
return resourceErrorTypeIllegalReservation.Generate(
"Resource.ReservationInfo.type must be set")
}
if r.Role == nil {
return resourceErrorTypeIllegalReservation.Generate(
"Resource.ReservationInfo.role must be set")
}
if _, err := roles.Parse(r.GetRole()); err != nil {
return resourceErrorTypeIllegalReservation.Generate(err.Error())
}
if r.GetRole() == "*" {
return resourceErrorTypeIllegalReservation.Generate(
"role '*' cannot be reserved")
}
}
// check that reservations are correctly refined
ancestor := rs[0].GetRole()
for i := 1; i < len(rs); i++ {
r := &rs[i]
if r.GetType() == Resource_ReservationInfo_STATIC {
return resourceErrorTypeIllegalReservation.Generate(
"a refined reservation cannot be STATIC")
}
child := r.GetRole()
if !roles.IsStrictSubroleOf(child, ancestor) {
return resourceErrorTypeIllegalReservation.Generate(fmt.Sprintf(
"role %q is not a refinement of %q", child, ancestor))
}
}
// Additionally, we allow the "pre-reservation-refinement" format to be set
// as long as there is only one reservation, and the `Resource.role` and
// `Resource.reservation` fields are consistent with the reservation.
if len(rs) == 1 {
if r := left.Role; r != nil && *r != rs[0].GetRole() {
return resourceErrorTypeIllegalReservation.Generate(fmt.Sprintf(
"'Resource.role' field with %q does not match the role %q in 'Resource.reservations'",
*r, rs[0].GetRole()))
}
switch rs[0].GetType() {
case Resource_ReservationInfo_STATIC:
if left.Reservation != nil {
return resourceErrorTypeIllegalReservation.Generate(
"'Resource.reservation' must not be set if the single reservation in 'Resource.reservations' is STATIC")
}
case Resource_ReservationInfo_DYNAMIC:
if (left.Role == nil) != (left.GetReservation() == nil) {
return resourceErrorTypeIllegalReservation.Generate(
"'Resource.role' and 'Resource.reservation' must both be set or both not be set if the single reservation in 'Resource.reservations' is DYNAMIC")
}
if r := left.GetReservation(); r != nil && r.GetPrincipal() != rs[0].GetPrincipal() {
return resourceErrorTypeIllegalReservation.Generate(fmt.Sprintf(
"'Resource.reservation.principal' with %q does not match the principal %q in 'Resource.reservations'",
r.GetPrincipal(), rs[0].GetPrincipal()))
}
if r := left.GetReservation(); r != nil && !r.GetLabels().Equivalent(rs[0].GetLabels()) {
return resourceErrorTypeIllegalReservation.Generate(fmt.Sprintf(
"'Resource.reservation.labels' with %q does not match the labels %q in 'Resource.reservations'",
r.GetLabels(), rs[0].GetLabels()))
}
case Resource_ReservationInfo_UNKNOWN:
return resourceErrorTypeIllegalReservation.Generate("Unsupported 'Resource.ReservationInfo.type'")
}
} else {
if r := left.Role; r != nil {
return resourceErrorTypeIllegalReservation.Generate(
"'Resource.role' must not be set if there is more than one reservation in 'Resource.reservations'")
}
if r := left.GetReservation(); r != nil {
return resourceErrorTypeIllegalReservation.Generate(
"'Resource.reservation' must not be set if there is more than one reservation in 'Resource.reservations'")
}
}
}
// Check that shareability is enabled for supported resource types.
// For now, it is for persistent volumes only.
// NOTE: We need to modify this once we extend shareability to other
// resource types.
if s := left.GetShared(); s != nil {
if left.GetName() != "disk" {
return resourceErrorTypeIllegalShare.Generate(fmt.Sprintf(
"Resource %q cannot be shared", left.GetName()))
}
if p := left.GetDisk().GetPersistence(); p == nil {
return resourceErrorTypeIllegalShare.Generate("only persistent volumes can be shared")
}
}
return nil
}
func (left *Resource_AllocationInfo) Equivalent(right *Resource_AllocationInfo) bool {
if (left == nil) != (right == nil) {
return false
} else if left == nil {
return true
}
if (left.Role == nil) != (right.Role == nil) {
return false
}
if left.Role != nil && *left.Role != *right.Role {
return false
}
return true
}
func (r *Resource_ReservationInfo) Equivalent(right *Resource_ReservationInfo) bool {
// TODO(jdef) should we consider equivalency of both pre- and post-refinement formats,
// such that a pre-refinement format could be the equivalent of a post-refinement format
// if defined just the right way?
if (r == nil) != (right == nil) {
return false
} else if r == nil {
return true
}
if (r.Type == nil) != (right.Type == nil) {
return false
}
if r.Type != nil && *r.Type != *right.Type {
return false
}
if (r.Role == nil) != (right.Role == nil) {
return false
}
if r.Role != nil && *r.Role != *right.Role {
return false
}
if (r.Principal == nil) != (right.Principal == nil) {
return false
}
if r.Principal != nil && *r.Principal != *right.Principal {
return false
}
return r.Labels.Equivalent(right.Labels)
}
func (left *Resource_DiskInfo) Equivalent(right *Resource_DiskInfo) bool {
// NOTE: We ignore 'volume' inside DiskInfo when doing comparison
// because it describes how this resource will be used which has
// nothing to do with the Resource object itself. A framework can
// use this resource and specify different 'volume' every time it
// uses it.
// see https://github.com/apache/mesos/blob/0.25.0/src/common/resources.cpp#L67
if (left == nil) != (right == nil) {
return false
}
if a, b := left.GetSource(), right.GetSource(); (a == nil) != (b == nil) {
return false
} else if a != nil {
if a.GetType() != b.GetType() {
return false
}
if aa, bb := a.GetMount(), b.GetMount(); (aa == nil) != (bb == nil) {
return false
} else if aa.GetRoot() != bb.GetRoot() {
return false
}
if aa, bb := a.GetPath(), b.GetPath(); (aa == nil) != (bb == nil) {
return false
} else if aa.GetRoot() != bb.GetRoot() {
return false
}
if aa, bb := a.GetID(), b.GetID(); aa != bb {
return false
}
if aa, bb := a.GetProfile(), b.GetProfile(); aa != bb {
return false
}
if aa, bb := a.GetMetadata(), b.GetMetadata(); (aa == nil) != (bb == nil) {
return false
} else if !labelList(aa.GetLabels()).Equivalent(labelList(bb.GetLabels())) {
return false
}
}
if a, b := left.GetPersistence(), right.GetPersistence(); (a == nil) != (b == nil) {
return false
} else if a != nil {
return a.GetID() == b.GetID()
}
return true
}
// Equivalent returns true if right is equivalent to left (differs from Equal in that
// deeply nested values are test for equivalence, not equality).
func (left *Resource) Equivalent(right Resource) bool {
if left == nil {
return right.IsEmpty()
}
if left.GetName() != right.GetName() ||
left.GetType() != right.GetType() ||
left.GetRole() != right.GetRole() {
return false
}
if a, b := left.GetAllocationInfo(), right.GetAllocationInfo(); !a.Equivalent(b) {
return false
}
if a, b := left.GetReservations(), right.GetReservations(); len(a) != len(b) {
return false
} else {
for i := range a {
ri := &a[i]
if !ri.Equivalent(&b[i]) {
return false
}
}
}
if !left.GetReservation().Equivalent(right.GetReservation()) {
return false
}
if !left.GetDisk().Equivalent(right.GetDisk()) {
return false
}
if (left.Revocable == nil) != (right.Revocable == nil) {
return false
}
if a, b := left.ProviderID, right.ProviderID; (a == nil) != (b == nil) {
return false
} else if a != nil && a.Value != b.Value {
return false
}
if a, b := left.Shared, right.Shared; (a == nil) != (b == nil) {
return false
}
switch left.GetType() {
case SCALAR:
return left.GetScalar().Compare(right.GetScalar()) == 0
case RANGES:
return Ranges(left.GetRanges().GetRange()).Equivalent(right.GetRanges().GetRange())
case SET:
return left.GetSet().Compare(right.GetSet()) == 0
default:
return false
}
}
// Addable tests if we can add two Resource objects together resulting in one
// valid Resource object. For example, two Resource objects with
// different name, type or role are not addable.
func (left *Resource) Addable(right Resource) bool {
if left == nil {
return true
}
if left.GetName() != right.GetName() ||
left.GetType() != right.GetType() ||
left.GetRole() != right.GetRole() {
return false
}
if a, b := left.GetShared(), right.GetShared(); (a == nil) != (b == nil) {
// shared has no fields
return false
}
if a, b := left.GetAllocationInfo(), right.GetAllocationInfo(); !a.Equivalent(b) {
return false
}
if !left.GetReservation().Equivalent(right.GetReservation()) {
return false
}
if a, b := left.Reservations, right.Reservations; len(a) != len(b) {
return false
} else {
for i := range a {
aa := &a[i]
if !aa.Equivalent(&b[i]) {
return false
}
}
}
if !left.GetDisk().Equivalent(right.GetDisk()) {
return false
}
if ls := left.GetDisk().GetSource(); ls != nil {
switch ls.GetType() {
case Resource_DiskInfo_Source_PATH:
// Two PATH resources can be added if their disks are identical
case Resource_DiskInfo_Source_BLOCK,
Resource_DiskInfo_Source_MOUNT:
// Two resources that represent exclusive 'MOUNT' or 'RAW' disks
// cannot be added together; this would defeat the exclusivity.
return false
case Resource_DiskInfo_Source_RAW:
// We can only add resources representing 'RAW' disks if
// they have no identity or are identical.
if ls.GetID() != "" {
return false
}
case Resource_DiskInfo_Source_UNKNOWN:
panic("unreachable")
}
}
// from apache/mesos: src/common/resources.cpp
// TODO(jieyu): Even if two Resource objects with DiskInfo have the
// same persistence ID, they cannot be added together. In fact, this
// shouldn't happen if we do not add resources from different
// namespaces (e.g., across slave). Consider adding a warning.
if left.GetDisk().GetPersistence() != nil {
return false
}
if (left.GetRevocable() == nil) != (right.GetRevocable() == nil) {
return false
}
if a, b := left.GetProviderID(), right.GetProviderID(); (a == nil) != (b == nil) {
return false
} else if a != nil && a.Value != b.Value {
return false
}
return true
}
// Subtractable tests if we can subtract "right" from "left" resulting in one
// valid Resource object. For example, two Resource objects with different
// name, type or role are not subtractable.
// NOTE: Set subtraction is always well defined, it does not require
// 'right' to be contained within 'left'. For example, assuming that
// "left = {1, 2}" and "right = {2, 3}", "left" and "right" are
// subtractable because "left - right = {1}". However, "left" does not
// contain "right".
func (left *Resource) Subtractable(right Resource) bool {
if left.GetName() != right.GetName() ||
left.GetType() != right.GetType() ||
left.GetRole() != right.GetRole() {
return false
}
if a, b := left.GetShared(), right.GetShared(); (a == nil) != (b == nil) {
// shared has no fields
return false
}
if a, b := left.GetAllocationInfo(), right.GetAllocationInfo(); !a.Equivalent(b) {
return false
}
if !left.GetReservation().Equivalent(right.GetReservation()) {
return false
}
if a, b := left.Reservations, right.Reservations; len(a) != len(b) {
return false
} else {
for i := range a {
aa := &a[i]
if !aa.Equivalent(&b[i]) {
return false
}
}
}
if !left.GetDisk().Equivalent(right.GetDisk()) {
return false
}
if ls := left.GetDisk().GetSource(); ls != nil {
switch ls.GetType() {
case Resource_DiskInfo_Source_PATH:
// Two PATH resources can be subtracted if their disks are identical
case Resource_DiskInfo_Source_BLOCK,
Resource_DiskInfo_Source_MOUNT:
// Two resources that represent exclusive 'MOUNT' or 'RAW' disks
// cannot be substracted from each other if they are not the same;
// this would defeat the exclusivity.
if !left.Equivalent(right) {
return false
}
case Resource_DiskInfo_Source_RAW:
// We can only add resources representing 'RAW' disks if
// they have no identity or refer to the same disk.
if ls.GetID() != "" && !left.Equivalent(right) {
return false
}
case Resource_DiskInfo_Source_UNKNOWN:
panic("unreachable")
}
}
// NOTE: For Resource objects that have DiskInfo, we can only do
// subtraction if they are **equal**.
if left.GetDisk().GetPersistence() != nil && !left.Equivalent(right) {
return false
}
if (left.GetRevocable() == nil) != (right.GetRevocable() == nil) {
return false
}
if a, b := left.GetProviderID(), right.GetProviderID(); (a == nil) != (b == nil) {
return false
} else if a != nil && a.Value != b.Value {
return false
}
return true
}
// Contains tests if "right" is contained in "left".
func (left Resource) Contains(right Resource) bool {
if !left.Subtractable(right) {
return false
}
switch left.GetType() {
case SCALAR:
return right.GetScalar().Compare(left.GetScalar()) <= 0
case RANGES:
return right.GetRanges().Compare(left.GetRanges()) <= 0
case SET:
return right.GetSet().Compare(left.GetSet()) <= 0
default:
return false
}
}
// Subtract removes right from left.
// This func panics if the resource types don't match.
func (left *Resource) Subtract(right Resource) {
switch right.checkType(left.GetType()) {
case SCALAR:
left.Scalar = left.GetScalar().Subtract(right.GetScalar())
case RANGES:
left.Ranges = left.GetRanges().Subtract(right.GetRanges())
case SET:
left.Set = left.GetSet().Subtract(right.GetSet())
}
}
// Add adds right to left.
// This func panics if the resource types don't match.
func (left *Resource) Add(right Resource) {
switch right.checkType(left.GetType()) {
case SCALAR:
left.Scalar = left.GetScalar().Add(right.GetScalar())
case RANGES:
left.Ranges = left.GetRanges().Add(right.GetRanges())
case SET:
left.Set = left.GetSet().Add(right.GetSet())
}
}
// checkType panics if the type of this resources != t
func (left *Resource) checkType(t Value_Type) Value_Type {
if left != nil && left.GetType() != t {
panic(fmt.Sprintf("expected type %v instead of %v", t, left.GetType()))
}
return t
}
// IsEmpty returns true if the value of this resource is equivalent to the zero-value,
// where a zero-length slice or map is equivalent to a nil reference to such.
func (left *Resource) IsEmpty() bool {
if left == nil {
return true
}
switch left.GetType() {
case SCALAR:
return left.GetScalar().GetValue() == 0
case RANGES:
return len(left.GetRanges().GetRange()) == 0
case SET:
return len(left.GetSet().GetItem()) == 0
}
return false
}
// IsUnreserved returns true if this resource neither statically or dynamically reserved.
// A resource is considered statically reserved if it has a non-default role.
func (left *Resource) IsUnreserved() bool {
// role != RoleDefault -> static reservation
// GetReservation() != nil -> dynamic reservation
// return {no-static-reservation} && {no-dynamic-reservation}
return (left.Role == nil || left.GetRole() == "*") && left.GetReservation() == nil && len(left.GetReservations()) == 0
}
// IsReserved returns true if this resource has been reserved for the given role.
// If role=="" then return true if there are no static or dynamic reservations for this resource.
// It's expected that this Resource has already been validated (see Validate).
func (left *Resource) IsReserved(role string) bool {
return !left.IsUnreserved() && (role == "" || role == left.ReservationRole())
}
// ReservationRole returns the role for which the resource is reserved. Callers should check the
// reservation status of the resource via IsReserved prior to invoking this func.
func (r *Resource) ReservationRole() string {
// if using reservation refinement, return the role of the last refinement
rs := r.GetReservations()
if x := len(rs); x > 0 {
return rs[x-1].GetRole()
}
// if using the old reservation API, role is a first class field of Resource
// (and it's never stored in Resource.Reservation).
return r.GetRole()
}
// IsAllocatableTo returns true if the resource may be allocated to the given role.
func (left *Resource) IsAllocatableTo(role string) bool {
if left.IsUnreserved() {
return true
}
r := left.ReservationRole()
return role == r || roles.IsStrictSubroleOf(role, r)
}
// IsDynamicallyReserved returns true if this resource has a non-nil reservation descriptor
func (left *Resource) IsDynamicallyReserved() bool {
if left.IsReserved("") {
if left.GetReservation() != nil {
return true
}
rs := left.GetReservations()
return rs[len(rs)-1].GetType() == Resource_ReservationInfo_DYNAMIC
}
return false
}
// IsRevocable returns true if this resource has a non-nil revocable descriptor
func (left *Resource) IsRevocable() bool {
return left.GetRevocable() != nil
}
// IsPersistentVolume returns true if this is a disk resource with a non-nil Persistence descriptor
func (left *Resource) IsPersistentVolume() bool {
return left.GetDisk().GetPersistence() != nil
}
// IsDisk returns true if this is a disk resource of the specified type.
func (left *Resource) IsDisk(t Resource_DiskInfo_Source_Type) bool {
if s := left.GetDisk().GetSource(); s != nil {
return s.GetType() == t
}
return false
}
// HasResourceProvider returns true if the given Resource object is provided by a resource provider.
func (left *Resource) HasResourceProvider() bool {
return left.GetProviderID() != nil
}
// ToUnreserved returns a (cloned) view of the Resources w/o any reservation data. It does not modify
// the receiver.
func (rs Resources) ToUnreserved() (result Resources) {
if rs == nil {
return nil
}
for i := range rs {
r := rs[i] // intentionally shallow-copy
r.Reservations = nil
r.Reservation = nil
r.Role = nil
result.Add1(r)
}
return
}
// PushReservation returns a cloned set of Resources w/ the given resource refinement.
// Panics if resources become invalid as a result of pushing the reservation (e.g. pre- and post-
// refinement modes are mixed).
func (rs Resources) PushReservation(ri Resource_ReservationInfo) (result Resources) {
push_next:
for i := range rs {
if rs[i].IsEmpty() {
continue
}
r := proto.Clone(&rs[i]).(*Resource) // we don't want to impact rs
r.Reservations = append(r.Reservations, *(proto.Clone(&ri).(*Resource_ReservationInfo)))
if err := r.Validate(); err != nil {
panic(err)
}
// unroll Add1 to avoid additional calls to Clone
rr := *r
for j := range result {
r2 := &result[j]
if r2.Addable(rr) {
r2.Add(rr)
continue push_next
}
}
// cannot be combined with an existing resource
result = append(result, rr)
}
return
}
// PopReservation returns a cloned set of Resources wherein the most recent reservation refeinement has been
// removed. Panics if for any resource in the collection there is no "last refinement" to remove.
func (rs Resources) PopReservation() (result Resources) {
pop_next:
for i := range rs {
r := &rs[i]
ls := len(r.Reservations)
if ls == 0 {
panic(fmt.Sprintf("no reservations exist for resource %q", r))
}
r = proto.Clone(r).(*Resource) // avoid modifying rs
r.Reservations[ls-1] = Resource_ReservationInfo{} // don't leak nested pointers
r.Reservations = r.Reservations[:ls-1] // shrink the slice
// unroll Add1 to avoid additional calls to Clone
rr := *r
for j := range result {
r2 := &result[j]
if r2.Addable(rr) {
r2.Add(rr)
continue pop_next
}
}
// cannot be combined with an existing resource
result = append(result, rr)
}
return
}
// Allocate sets the AllocationInfo for the resource, panics if role is "".
func (r *Resource) Allocate(role string) {
if role == "" {
panic(fmt.Sprintf("cannot allocate resource to an empty-string role: %q", r))
}
r.AllocationInfo = &Resource_AllocationInfo{Role: &role}
}
// Unallocate clears the AllocationInfo for the resource.
func (r *Resource) Unallocate() {
r.AllocationInfo = nil
}
// Allocate sets the AllocationInfo for all the resources.
// Returns a reference to the receiver to allow for chaining.
func (rs Resources) Allocate(role string) Resources {
if role == "" {
panic(fmt.Sprintf("cannot allocate resources to an empty-string role: %q", rs))
}
for i := range rs {
rs[i].AllocationInfo = &Resource_AllocationInfo{Role: &role}
}
return rs
}
// Unallocate clears the AllocationInfo for all the resources.
// Returns a reference to the receiver to allow for chaining.
func (rs Resources) Unallocate() Resources {
for i := range rs {
rs[i].AllocationInfo = nil
}
return rs
}