mirror of https://github.com/k3s-io/k3s
1146 lines
34 KiB
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
|
|
}
|