diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b02d6bcaad3..923f3f097c6 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -115,18 +115,23 @@ }, { "ImportPath": "github.com/appc/spec/schema", - "Comment": "v0.8.1-6-gab50d12", - "Rev": "ab50d12e88f57788bf84b83fef2be236eb1fcc0b" + "Comment": "v0.8.9-17-gfc380db", + "Rev": "fc380db5fc13c6dd71a5b0bf2af0d182865d1b1d" }, { "ImportPath": "github.com/appc/spec/schema/common", - "Comment": "v0.8.1-6-gab50d12", - "Rev": "ab50d12e88f57788bf84b83fef2be236eb1fcc0b" + "Comment": "v0.8.9-17-gfc380db", + "Rev": "fc380db5fc13c6dd71a5b0bf2af0d182865d1b1d" }, { "ImportPath": "github.com/appc/spec/schema/types", - "Comment": "v0.8.1-6-gab50d12", - "Rev": "ab50d12e88f57788bf84b83fef2be236eb1fcc0b" + "Comment": "v0.8.9-17-gfc380db", + "Rev": "fc380db5fc13c6dd71a5b0bf2af0d182865d1b1d" + }, + { + "ImportPath": "github.com/appc/spec/schema/types/resource", + "Comment": "v0.8.9-17-gfc380db", + "Rev": "fc380db5fc13c6dd71a5b0bf2af0d182865d1b1d" }, { "ImportPath": "github.com/armon/circbuf", diff --git a/vendor/github.com/appc/spec/schema/pod.go b/vendor/github.com/appc/spec/schema/pod.go index d4792ff3b40..9ed6c9e0a66 100644 --- a/vendor/github.com/appc/spec/schema/pod.go +++ b/vendor/github.com/appc/spec/schema/pod.go @@ -28,13 +28,15 @@ import ( const PodManifestKind = types.ACKind("PodManifest") type PodManifest struct { - ACVersion types.SemVer `json:"acVersion"` - ACKind types.ACKind `json:"acKind"` - Apps AppList `json:"apps"` - Volumes []types.Volume `json:"volumes"` - Isolators []types.Isolator `json:"isolators"` - Annotations types.Annotations `json:"annotations"` - Ports []types.ExposedPort `json:"ports"` + ACVersion types.SemVer `json:"acVersion"` + ACKind types.ACKind `json:"acKind"` + Apps AppList `json:"apps"` + Volumes []types.Volume `json:"volumes"` + Isolators []types.Isolator `json:"isolators"` + Annotations types.Annotations `json:"annotations"` + Ports []types.ExposedPort `json:"ports"` + UserAnnotations types.UserAnnotations `json:"userAnnotations,omitempty"` + UserLabels types.UserLabels `json:"userLabels,omitempty"` } // podManifest is a model to facilitate extra validation during the @@ -135,9 +137,12 @@ func (al AppList) Get(name types.ACName) *RuntimeApp { // Mount describes the mapping between a volume and the path it is mounted // inside of an app's filesystem. +// The AppVolume is optional. If missing, the pod-level Volume of the +// same name shall be used. type Mount struct { - Volume types.ACName `json:"volume"` - Path string `json:"path"` + Volume types.ACName `json:"volume"` + Path string `json:"path"` + AppVolume *types.Volume `json:"appVolume,omitempty"` } func (r Mount) assertValid() error { diff --git a/vendor/github.com/appc/spec/schema/types/app.go b/vendor/github.com/appc/spec/schema/types/app.go index df13bf1c3a4..0ef68c892f8 100644 --- a/vendor/github.com/appc/spec/schema/types/app.go +++ b/vendor/github.com/appc/spec/schema/types/app.go @@ -22,16 +22,18 @@ import ( ) type App struct { - Exec Exec `json:"exec"` - EventHandlers []EventHandler `json:"eventHandlers,omitempty"` - User string `json:"user"` - Group string `json:"group"` - SupplementaryGIDs []int `json:"supplementaryGIDs,omitempty"` - WorkingDirectory string `json:"workingDirectory,omitempty"` - Environment Environment `json:"environment,omitempty"` - MountPoints []MountPoint `json:"mountPoints,omitempty"` - Ports []Port `json:"ports,omitempty"` - Isolators Isolators `json:"isolators,omitempty"` + Exec Exec `json:"exec"` + EventHandlers []EventHandler `json:"eventHandlers,omitempty"` + User string `json:"user"` + Group string `json:"group"` + SupplementaryGIDs []int `json:"supplementaryGIDs,omitempty"` + WorkingDirectory string `json:"workingDirectory,omitempty"` + Environment Environment `json:"environment,omitempty"` + MountPoints []MountPoint `json:"mountPoints,omitempty"` + Ports []Port `json:"ports,omitempty"` + Isolators Isolators `json:"isolators,omitempty"` + UserAnnotations UserAnnotations `json:"userAnnotations,omitempty"` + UserLabels UserLabels `json:"userLabels,omitempty"` } // app is a model to facilitate extra validation during the @@ -86,5 +88,8 @@ func (a *App) assertValid() error { if err := a.Environment.assertValid(); err != nil { return err } + if err := a.Isolators.assertValid(); err != nil { + return err + } return nil } diff --git a/vendor/github.com/appc/spec/schema/types/isolator.go b/vendor/github.com/appc/spec/schema/types/isolator.go index ecdab0088bb..fcd1801e64e 100644 --- a/vendor/github.com/appc/spec/schema/types/isolator.go +++ b/vendor/github.com/appc/spec/schema/types/isolator.go @@ -16,10 +16,19 @@ package types import ( "encoding/json" + "errors" + "fmt" ) var ( isolatorMap map[ACIdentifier]IsolatorValueConstructor + + // ErrIncompatibleIsolator is returned whenever an Isolators set contains + // conflicting IsolatorValue instances + ErrIncompatibleIsolator = errors.New("isolators set contains incompatible types") + // ErrInvalidIsolator is returned upon validation failures due to improper + // or partially constructed Isolator instances (eg. from incomplete direct construction) + ErrInvalidIsolator = errors.New("invalid isolator") ) func init() { @@ -40,6 +49,33 @@ func AddIsolatorName(n ACIdentifier, ns map[ACIdentifier]struct{}) { // and PodManifest schemas. type Isolators []Isolator +// assertValid checks that every single isolator is valid and that +// the whole set is well built +func (isolators Isolators) assertValid() error { + typesMap := make(map[ACIdentifier]bool) + for _, i := range isolators { + v := i.Value() + if v == nil { + return ErrInvalidIsolator + } + if err := v.AssertValid(); err != nil { + return err + } + if _, ok := typesMap[i.Name]; ok { + if !v.multipleAllowed() { + return fmt.Errorf(`isolators set contains too many instances of type %s"`, i.Name) + } + } + for _, c := range v.Conflicts() { + if _, found := typesMap[c]; found { + return ErrIncompatibleIsolator + } + } + typesMap[i.Name] = true + } + return nil +} + // GetByName returns the last isolator in the list by the given name. func (is *Isolators) GetByName(name ACIdentifier) *Isolator { var i Isolator @@ -52,6 +88,22 @@ func (is *Isolators) GetByName(name ACIdentifier) *Isolator { return nil } +// ReplaceIsolatorsByName overrides matching isolator types with a new +// isolator, deleting them all and appending the new one instead +func (is *Isolators) ReplaceIsolatorsByName(newIs Isolator, oldNames []ACIdentifier) { + var i Isolator + for j := len(*is) - 1; j >= 0; j-- { + i = []Isolator(*is)[j] + for _, name := range oldNames { + if i.Name == name { + *is = append((*is)[:j], (*is)[j+1:]...) + } + } + } + *is = append((*is)[:], newIs) + return +} + // Unrecognized returns a set of isolators that are not recognized. // An isolator is not recognized if it has not had an associated // constructor registered with AddIsolatorValueConstructor. @@ -69,8 +121,17 @@ func (is *Isolators) Unrecognized() Isolators { // serialized as any arbitrary JSON blob. Specific Isolator types should // implement this interface to facilitate unmarshalling and validation. type IsolatorValue interface { + // UnmarshalJSON unserialize a JSON-encoded isolator UnmarshalJSON(b []byte) error + // AssertValid returns a non-nil error value if an IsolatorValue is not valid + // according to appc spec AssertValid() error + // Conflicts returns a list of conflicting isolators types, which cannot co-exist + // together with this IsolatorValue + Conflicts() []ACIdentifier + // multipleAllowed specifies whether multiple isolator instances are allowed + // for this isolator type + multipleAllowed() bool } // Isolator is a model for unmarshalling isolator types from their JSON-encoded diff --git a/vendor/github.com/appc/spec/schema/types/isolator_linux_specific.go b/vendor/github.com/appc/spec/schema/types/isolator_linux_specific.go index b390ed9187b..3d5dc9fa285 100644 --- a/vendor/github.com/appc/spec/schema/types/isolator_linux_specific.go +++ b/vendor/github.com/appc/spec/schema/types/isolator_linux_specific.go @@ -17,11 +17,20 @@ package types import ( "encoding/json" "errors" + "fmt" + "strings" + "unicode" ) const ( LinuxCapabilitiesRetainSetName = "os/linux/capabilities-retain-set" LinuxCapabilitiesRevokeSetName = "os/linux/capabilities-remove-set" + LinuxNoNewPrivilegesName = "os/linux/no-new-privileges" + LinuxSeccompRemoveSetName = "os/linux/seccomp-remove-set" + LinuxSeccompRetainSetName = "os/linux/seccomp-retain-set" + LinuxOOMScoreAdjName = "os/linux/oom-score-adj" + LinuxCPUSharesName = "os/linux/cpu-shares" + LinuxSELinuxContextName = "os/linux/selinux-context" ) var LinuxIsolatorNames = make(map[ACIdentifier]struct{}) @@ -30,12 +39,49 @@ func init() { for name, con := range map[ACIdentifier]IsolatorValueConstructor{ LinuxCapabilitiesRevokeSetName: func() IsolatorValue { return &LinuxCapabilitiesRevokeSet{} }, LinuxCapabilitiesRetainSetName: func() IsolatorValue { return &LinuxCapabilitiesRetainSet{} }, + LinuxNoNewPrivilegesName: func() IsolatorValue { v := LinuxNoNewPrivileges(false); return &v }, + LinuxOOMScoreAdjName: func() IsolatorValue { v := LinuxOOMScoreAdj(0); return &v }, + LinuxCPUSharesName: func() IsolatorValue { v := LinuxCPUShares(1024); return &v }, + LinuxSeccompRemoveSetName: func() IsolatorValue { return &LinuxSeccompRemoveSet{} }, + LinuxSeccompRetainSetName: func() IsolatorValue { return &LinuxSeccompRetainSet{} }, + LinuxSELinuxContextName: func() IsolatorValue { return &LinuxSELinuxContext{} }, } { AddIsolatorName(name, LinuxIsolatorNames) AddIsolatorValueConstructor(name, con) } } +type LinuxNoNewPrivileges bool + +func (l LinuxNoNewPrivileges) AssertValid() error { + return nil +} + +// TODO(lucab): both need to be clarified in spec, +// see https://github.com/appc/spec/issues/625 +func (l LinuxNoNewPrivileges) multipleAllowed() bool { + return true +} +func (l LinuxNoNewPrivileges) Conflicts() []ACIdentifier { + return nil +} + +func (l *LinuxNoNewPrivileges) UnmarshalJSON(b []byte) error { + var v bool + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + + *l = LinuxNoNewPrivileges(v) + + return nil +} + +type AsIsolator interface { + AsIsolator() (*Isolator, error) +} + type LinuxCapabilitiesSet interface { Set() []LinuxCapability AssertValid() error @@ -58,6 +104,15 @@ func (l linuxCapabilitiesSetBase) AssertValid() error { return nil } +// TODO(lucab): both need to be clarified in spec, +// see https://github.com/appc/spec/issues/625 +func (l linuxCapabilitiesSetBase) multipleAllowed() bool { + return true +} +func (l linuxCapabilitiesSetBase) Conflicts() []ACIdentifier { + return nil +} + func (l *linuxCapabilitiesSetBase) UnmarshalJSON(b []byte) error { var v linuxCapabilitiesSetValue err := json.Unmarshal(b, &v) @@ -95,17 +150,17 @@ func NewLinuxCapabilitiesRetainSet(caps ...string) (*LinuxCapabilitiesRetainSet, return &l, nil } -func (l LinuxCapabilitiesRetainSet) AsIsolator() Isolator { +func (l LinuxCapabilitiesRetainSet) AsIsolator() (*Isolator, error) { b, err := json.Marshal(l.linuxCapabilitiesSetBase.val) if err != nil { - panic(err) + return nil, err } rm := json.RawMessage(b) - return Isolator{ + return &Isolator{ Name: LinuxCapabilitiesRetainSetName, ValueRaw: &rm, value: &l, - } + }, nil } type LinuxCapabilitiesRevokeSet struct { @@ -129,15 +184,346 @@ func NewLinuxCapabilitiesRevokeSet(caps ...string) (*LinuxCapabilitiesRevokeSet, return &l, nil } -func (l LinuxCapabilitiesRevokeSet) AsIsolator() Isolator { +func (l LinuxCapabilitiesRevokeSet) AsIsolator() (*Isolator, error) { b, err := json.Marshal(l.linuxCapabilitiesSetBase.val) + if err != nil { + return nil, err + } + rm := json.RawMessage(b) + return &Isolator{ + Name: LinuxCapabilitiesRevokeSetName, + ValueRaw: &rm, + value: &l, + }, nil +} + +type LinuxSeccompSet interface { + Set() []LinuxSeccompEntry + Errno() LinuxSeccompErrno + AssertValid() error +} + +type LinuxSeccompEntry string +type LinuxSeccompErrno string + +type linuxSeccompValue struct { + Set []LinuxSeccompEntry `json:"set"` + Errno LinuxSeccompErrno `json:"errno"` +} + +type linuxSeccompBase struct { + val linuxSeccompValue +} + +func (l linuxSeccompBase) multipleAllowed() bool { + return false +} + +func (l linuxSeccompBase) AssertValid() error { + if len(l.val.Set) == 0 { + return errors.New("set must be non-empty") + } + if l.val.Errno == "" { + return nil + } + for _, c := range l.val.Errno { + if !unicode.IsUpper(c) { + return errors.New("errno must be an upper case string") + } + } + return nil +} + +func (l *linuxSeccompBase) UnmarshalJSON(b []byte) error { + var v linuxSeccompValue + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + l.val = v + return nil +} + +func (l linuxSeccompBase) Set() []LinuxSeccompEntry { + return l.val.Set +} + +func (l linuxSeccompBase) Errno() LinuxSeccompErrno { + return l.val.Errno +} + +type LinuxSeccompRetainSet struct { + linuxSeccompBase +} + +func (l LinuxSeccompRetainSet) Conflicts() []ACIdentifier { + return []ACIdentifier{LinuxSeccompRemoveSetName} +} + +func NewLinuxSeccompRetainSet(errno string, syscall ...string) (*LinuxSeccompRetainSet, error) { + l := LinuxSeccompRetainSet{ + linuxSeccompBase{ + linuxSeccompValue{ + make([]LinuxSeccompEntry, len(syscall)), + LinuxSeccompErrno(errno), + }, + }, + } + for i, c := range syscall { + l.linuxSeccompBase.val.Set[i] = LinuxSeccompEntry(c) + } + if err := l.AssertValid(); err != nil { + return nil, err + } + return &l, nil +} + +func (l LinuxSeccompRetainSet) AsIsolator() (*Isolator, error) { + b, err := json.Marshal(l.linuxSeccompBase.val) + if err != nil { + return nil, err + } + rm := json.RawMessage(b) + return &Isolator{ + Name: LinuxSeccompRetainSetName, + ValueRaw: &rm, + value: &l, + }, nil +} + +type LinuxSeccompRemoveSet struct { + linuxSeccompBase +} + +func (l LinuxSeccompRemoveSet) Conflicts() []ACIdentifier { + return []ACIdentifier{LinuxSeccompRetainSetName} +} + +func NewLinuxSeccompRemoveSet(errno string, syscall ...string) (*LinuxSeccompRemoveSet, error) { + l := LinuxSeccompRemoveSet{ + linuxSeccompBase{ + linuxSeccompValue{ + make([]LinuxSeccompEntry, len(syscall)), + LinuxSeccompErrno(errno), + }, + }, + } + for i, c := range syscall { + l.linuxSeccompBase.val.Set[i] = LinuxSeccompEntry(c) + } + if err := l.AssertValid(); err != nil { + return nil, err + } + return &l, nil +} + +func (l LinuxSeccompRemoveSet) AsIsolator() (*Isolator, error) { + b, err := json.Marshal(l.linuxSeccompBase.val) + if err != nil { + return nil, err + } + rm := json.RawMessage(b) + return &Isolator{ + Name: LinuxSeccompRemoveSetName, + ValueRaw: &rm, + value: &l, + }, nil +} + +// LinuxCPUShares assigns the CPU time share weight to the processes executed. +// See https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#CPUShares=weight, +// https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt +type LinuxCPUShares int + +func NewLinuxCPUShares(val int) (*LinuxCPUShares, error) { + l := LinuxCPUShares(val) + if err := l.AssertValid(); err != nil { + return nil, err + } + + return &l, nil +} + +func (l LinuxCPUShares) AssertValid() error { + if l < 2 || l > 262144 { + return fmt.Errorf("%s must be between 2 and 262144, got %d", LinuxCPUSharesName, l) + } + return nil +} + +func (l LinuxCPUShares) multipleAllowed() bool { + return false +} + +func (l LinuxCPUShares) Conflicts() []ACIdentifier { + return nil +} + +func (l *LinuxCPUShares) UnmarshalJSON(b []byte) error { + var v int + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + + *l = LinuxCPUShares(v) + return nil +} + +func (l LinuxCPUShares) AsIsolator() Isolator { + b, err := json.Marshal(l) if err != nil { panic(err) } rm := json.RawMessage(b) return Isolator{ - Name: LinuxCapabilitiesRevokeSetName, + Name: LinuxCPUSharesName, ValueRaw: &rm, value: &l, } } + +// LinuxOOMScoreAdj is equivalent to /proc/[pid]/oom_score_adj +type LinuxOOMScoreAdj int // -1000 to 1000 + +func NewLinuxOOMScoreAdj(val int) (*LinuxOOMScoreAdj, error) { + l := LinuxOOMScoreAdj(val) + if err := l.AssertValid(); err != nil { + return nil, err + } + + return &l, nil +} + +func (l LinuxOOMScoreAdj) AssertValid() error { + if l < -1000 || l > 1000 { + return fmt.Errorf("%s must be between -1000 and 1000, got %d", LinuxOOMScoreAdjName, l) + } + return nil +} + +func (l LinuxOOMScoreAdj) multipleAllowed() bool { + return false +} + +func (l LinuxOOMScoreAdj) Conflicts() []ACIdentifier { + return nil +} + +func (l *LinuxOOMScoreAdj) UnmarshalJSON(b []byte) error { + var v int + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + + *l = LinuxOOMScoreAdj(v) + return nil +} + +func (l LinuxOOMScoreAdj) AsIsolator() Isolator { + b, err := json.Marshal(l) + if err != nil { + panic(err) + } + rm := json.RawMessage(b) + return Isolator{ + Name: LinuxOOMScoreAdjName, + ValueRaw: &rm, + value: &l, + } +} + +type LinuxSELinuxUser string +type LinuxSELinuxRole string +type LinuxSELinuxType string +type LinuxSELinuxLevel string + +type linuxSELinuxValue struct { + User LinuxSELinuxUser `json:"user"` + Role LinuxSELinuxRole `json:"role"` + Type LinuxSELinuxType `json:"type"` + Level LinuxSELinuxLevel `json:"level"` +} + +type LinuxSELinuxContext struct { + val linuxSELinuxValue +} + +func (l LinuxSELinuxContext) AssertValid() error { + if l.val.User == "" || strings.Contains(string(l.val.User), ":") { + return fmt.Errorf("invalid user value %q", l.val.User) + } + if l.val.Role == "" || strings.Contains(string(l.val.Role), ":") { + return fmt.Errorf("invalid role value %q", l.val.Role) + } + if l.val.Type == "" || strings.Contains(string(l.val.Type), ":") { + return fmt.Errorf("invalid type value %q", l.val.Type) + } + if l.val.Level == "" { + return fmt.Errorf("invalid level value %q", l.val.Level) + } + return nil +} + +func (l *LinuxSELinuxContext) UnmarshalJSON(b []byte) error { + var v linuxSELinuxValue + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + l.val = v + return nil +} + +func (l LinuxSELinuxContext) User() LinuxSELinuxUser { + return l.val.User +} + +func (l LinuxSELinuxContext) Role() LinuxSELinuxRole { + return l.val.Role +} + +func (l LinuxSELinuxContext) Type() LinuxSELinuxType { + return l.val.Type +} + +func (l LinuxSELinuxContext) Level() LinuxSELinuxLevel { + return l.val.Level +} + +func (l LinuxSELinuxContext) multipleAllowed() bool { + return false +} + +func (l LinuxSELinuxContext) Conflicts() []ACIdentifier { + return nil +} + +func NewLinuxSELinuxContext(selinuxUser, selinuxRole, selinuxType, selinuxLevel string) (*LinuxSELinuxContext, error) { + l := LinuxSELinuxContext{ + linuxSELinuxValue{ + LinuxSELinuxUser(selinuxUser), + LinuxSELinuxRole(selinuxRole), + LinuxSELinuxType(selinuxType), + LinuxSELinuxLevel(selinuxLevel), + }, + } + if err := l.AssertValid(); err != nil { + return nil, err + } + return &l, nil +} + +func (l LinuxSELinuxContext) AsIsolator() (*Isolator, error) { + b, err := json.Marshal(l.val) + if err != nil { + return nil, err + } + rm := json.RawMessage(b) + return &Isolator{ + Name: LinuxSELinuxContextName, + ValueRaw: &rm, + value: &l, + }, nil +} diff --git a/vendor/github.com/appc/spec/schema/types/isolator_resources.go b/vendor/github.com/appc/spec/schema/types/isolator_resources.go index 2ca9f4c486f..2ec250d4f97 100644 --- a/vendor/github.com/appc/spec/schema/types/isolator_resources.go +++ b/vendor/github.com/appc/spec/schema/types/isolator_resources.go @@ -19,7 +19,7 @@ import ( "errors" "fmt" - "k8s.io/apimachinery/pkg/api/resource" + "github.com/appc/spec/schema/types/resource" ) var ( @@ -85,6 +85,15 @@ func (r ResourceBase) AssertValid() error { return nil } +// TODO(lucab): both need to be clarified in spec, +// see https://github.com/appc/spec/issues/625 +func (l ResourceBase) multipleAllowed() bool { + return true +} +func (l ResourceBase) Conflicts() []ACIdentifier { + return nil +} + type ResourceBlockBandwidth struct { ResourceBase } diff --git a/vendor/github.com/appc/spec/schema/types/isolator_unix.go b/vendor/github.com/appc/spec/schema/types/isolator_unix.go new file mode 100644 index 00000000000..ac0111a7deb --- /dev/null +++ b/vendor/github.com/appc/spec/schema/types/isolator_unix.go @@ -0,0 +1,83 @@ +// Copyright 2016 The appc Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" +) + +var ( + UnixIsolatorNames = make(map[ACIdentifier]struct{}) +) + +const ( + //TODO(lucab): add "ulimit" isolators + UnixSysctlName = "os/unix/sysctl" +) + +func init() { + for name, con := range map[ACIdentifier]IsolatorValueConstructor{ + UnixSysctlName: func() IsolatorValue { return &UnixSysctl{} }, + } { + AddIsolatorName(name, UnixIsolatorNames) + AddIsolatorValueConstructor(name, con) + } +} + +type UnixSysctl map[string]string + +func (s *UnixSysctl) UnmarshalJSON(b []byte) error { + var v map[string]string + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + *s = UnixSysctl(v) + return err +} + +func (s UnixSysctl) AssertValid() error { + return nil +} + +func (s UnixSysctl) multipleAllowed() bool { + return false +} +func (s UnixSysctl) Conflicts() []ACIdentifier { + return nil +} + +func (s UnixSysctl) AsIsolator() Isolator { + isol := isolatorMap[UnixSysctlName]() + + b, err := json.Marshal(s) + if err != nil { + panic(err) + } + valRaw := json.RawMessage(b) + return Isolator{ + Name: UnixSysctlName, + ValueRaw: &valRaw, + value: isol, + } +} + +func NewUnixSysctlIsolator(cfg map[string]string) (*UnixSysctl, error) { + s := UnixSysctl(cfg) + if err := s.AssertValid(); err != nil { + return nil, err + } + return &s, nil +} diff --git a/vendor/github.com/appc/spec/schema/types/labels.go b/vendor/github.com/appc/spec/schema/types/labels.go index ebd2bb1a983..89fa4285d4a 100644 --- a/vendor/github.com/appc/spec/schema/types/labels.go +++ b/vendor/github.com/appc/spec/schema/types/labels.go @@ -21,7 +21,7 @@ import ( ) var ValidOSArch = map[string][]string{ - "linux": {"amd64", "i386", "aarch64", "aarch64_be", "armv6l", "armv7l", "armv7b"}, + "linux": {"amd64", "i386", "aarch64", "aarch64_be", "armv6l", "armv7l", "armv7b", "ppc64", "ppc64le", "s390x"}, "freebsd": {"amd64", "i386", "arm"}, "darwin": {"x86_64", "i386"}, } @@ -35,6 +35,17 @@ type Label struct { Value string `json:"value"` } +// {appc,go}ArchTuple are internal helper types used to translate arch tuple between go and appc +type appcArchTuple struct { + appcOs string + appcArch string +} +type goArchTuple struct { + goOs string + goArch string + goArchFlavor string +} + // IsValidOsArch checks if a OS-architecture combination is valid given a map // of valid OS-architectures func IsValidOSArch(labels map[ACIdentifier]string, validOSArch map[string][]string) error { @@ -132,3 +143,64 @@ func LabelsFromMap(labelsMap map[ACIdentifier]string) (Labels, error) { } return labels, nil } + +// ToAppcOSArch translates a Golang arch tuple (OS, architecture, flavor) into +// an appc arch tuple (OS, architecture) +func ToAppcOSArch(goOs string, goArch string, goArchFlavor string) (appcOs string, appcArch string, e error) { + tabularAppcToGo := map[goArchTuple]appcArchTuple{ + {"linux", "amd64", ""}: {"linux", "amd64"}, + {"linux", "386", ""}: {"linux", "i386"}, + {"linux", "arm64", ""}: {"linux", "aarch64"}, + {"linux", "arm", ""}: {"linux", "armv6l"}, + {"linux", "arm", "6"}: {"linux", "armv6l"}, + {"linux", "arm", "7"}: {"linux", "armv7l"}, + {"linux", "ppc64", ""}: {"linux", "ppc64"}, + {"linux", "ppc64le", ""}: {"linux", "ppc64le"}, + {"linux", "s390x", ""}: {"linux", "s390x"}, + + {"freebsd", "amd64", ""}: {"freebsd", "amd64"}, + {"freebsd", "386", ""}: {"freebsd", "i386"}, + {"freebsd", "arm", ""}: {"freebsd", "arm"}, + {"freebsd", "arm", "5"}: {"freebsd", "arm"}, + {"freebsd", "arm", "6"}: {"freebsd", "arm"}, + {"freebsd", "arm", "7"}: {"freebsd", "arm"}, + + {"darwin", "amd64", ""}: {"darwin", "x86_64"}, + {"darwin", "386", ""}: {"darwin", "i386"}, + } + archTuple, ok := tabularAppcToGo[goArchTuple{goOs, goArch, goArchFlavor}] + if !ok { + return "", "", fmt.Errorf("unknown arch tuple: %q - %q - %q", goOs, goArch, goArchFlavor) + } + return archTuple.appcOs, archTuple.appcArch, nil +} + +// ToGoOSArch translates an appc arch tuple (OS, architecture) into +// a Golang arch tuple (OS, architecture, flavor) +func ToGoOSArch(appcOs string, appcArch string) (goOs string, goArch string, goArchFlavor string, e error) { + tabularGoToAppc := map[appcArchTuple]goArchTuple{ + // {"linux", "aarch64_be"}: nil, + // {"linux", "armv7b"}: nil, + {"linux", "aarch64"}: {"linux", "arm64", ""}, + {"linux", "amd64"}: {"linux", "amd64", ""}, + {"linux", "armv6l"}: {"linux", "arm", "6"}, + {"linux", "armv7l"}: {"linux", "arm", "7"}, + {"linux", "i386"}: {"linux", "386", ""}, + {"linux", "ppc64"}: {"linux", "ppc64", ""}, + {"linux", "ppc64le"}: {"linux", "ppc64le", ""}, + {"linux", "s390x"}: {"linux", "s390x", ""}, + + {"freebsd", "amd64"}: {"freebsd", "amd64", ""}, + {"freebsd", "arm"}: {"freebsd", "arm", "6"}, + {"freebsd", "386"}: {"freebsd", "i386", ""}, + + {"darwin", "amd64"}: {"darwin", "x86_64", ""}, + {"darwin", "386"}: {"darwin", "i386", ""}, + } + + archTuple, ok := tabularGoToAppc[appcArchTuple{appcOs, appcArch}] + if !ok { + return "", "", "", fmt.Errorf("unknown arch tuple: %q - %q", appcOs, appcArch) + } + return archTuple.goOs, archTuple.goArch, archTuple.goArchFlavor, nil +} diff --git a/vendor/github.com/appc/spec/schema/types/mountpoint.go b/vendor/github.com/appc/spec/schema/types/mountpoint.go index 80eab945638..9d4e52b3d0d 100644 --- a/vendor/github.com/appc/spec/schema/types/mountpoint.go +++ b/vendor/github.com/appc/spec/schema/types/mountpoint.go @@ -23,6 +23,7 @@ import ( "github.com/appc/spec/schema/common" ) +// MountPoint is the application-side manifestation of a Volume. type MountPoint struct { Name ACName `json:"name"` Path string `json:"path"` diff --git a/vendor/github.com/appc/spec/schema/types/port.go b/vendor/github.com/appc/spec/schema/types/port.go index 78d6de4ce18..2d54515037a 100644 --- a/vendor/github.com/appc/spec/schema/types/port.go +++ b/vendor/github.com/appc/spec/schema/types/port.go @@ -18,12 +18,15 @@ import ( "encoding/json" "errors" "fmt" + "net" "net/url" "strconv" "github.com/appc/spec/schema/common" ) +// Port represents a port as offered by an application *inside* +// the pod. type Port struct { Name ACName `json:"name"` Protocol string `json:"protocol"` @@ -32,9 +35,14 @@ type Port struct { SocketActivated bool `json:"socketActivated"` } +// ExposedPort represents a port listening on the host side. +// The PodPort is optional -- if missing, then try and find the pod-side +// information by matching names type ExposedPort struct { Name ACName `json:"name"` HostPort uint `json:"hostPort"` + HostIP net.IP `json:"hostIP,omitempty"` // optional + PodPort *Port `json:"podPort,omitempty"` // optional. If missing, try and find a corresponding App's port } type port Port diff --git a/vendor/github.com/appc/spec/schema/types/resource/README.md b/vendor/github.com/appc/spec/schema/types/resource/README.md new file mode 100644 index 00000000000..b3c16d25127 --- /dev/null +++ b/vendor/github.com/appc/spec/schema/types/resource/README.md @@ -0,0 +1,4 @@ +This package was copied in from the Kubernetes repo to avoid a cyclic +dependency. These files were taken from master from +github.com/kubernetes/kubernetes at commit hash +b0deb2eb8f4037421077f77cb163dbb4c0a2a9f5. diff --git a/vendor/github.com/appc/spec/schema/types/resource/amount.go b/vendor/github.com/appc/spec/schema/types/resource/amount.go new file mode 100644 index 00000000000..6ae823a0229 --- /dev/null +++ b/vendor/github.com/appc/spec/schema/types/resource/amount.go @@ -0,0 +1,298 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "math/big" + "strconv" + + inf "gopkg.in/inf.v0" +) + +// Scale is used for getting and setting the base-10 scaled value. +// Base-2 scales are omitted for mathematical simplicity. +// See Quantity.ScaledValue for more details. +type Scale int32 + +// infScale adapts a Scale value to an inf.Scale value. +func (s Scale) infScale() inf.Scale { + return inf.Scale(-s) // inf.Scale is upside-down +} + +const ( + Nano Scale = -9 + Micro Scale = -6 + Milli Scale = -3 + Kilo Scale = 3 + Mega Scale = 6 + Giga Scale = 9 + Tera Scale = 12 + Peta Scale = 15 + Exa Scale = 18 +) + +var ( + Zero = int64Amount{} + + // Used by quantity strings - treat as read only + zeroBytes = []byte("0") +) + +// int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster +// than operations on inf.Dec for values that can be represented as int64. +type int64Amount struct { + value int64 + scale Scale +} + +// Sign returns 0 if the value is zero, -1 if it is less than 0, or 1 if it is greater than 0. +func (a int64Amount) Sign() int { + switch { + case a.value == 0: + return 0 + case a.value > 0: + return 1 + default: + return -1 + } +} + +// AsInt64 returns the current amount as an int64 at scale 0, or false if the value cannot be +// represented in an int64 OR would result in a loss of precision. This method is intended as +// an optimization to avoid calling AsDec. +func (a int64Amount) AsInt64() (int64, bool) { + if a.scale == 0 { + return a.value, true + } + if a.scale < 0 { + // TODO: attempt to reduce factors, although it is assumed that factors are reduced prior + // to the int64Amount being created. + return 0, false + } + return positiveScaleInt64(a.value, a.scale) +} + +// AsScaledInt64 returns an int64 representing the value of this amount at the specified scale, +// rounding up, or false if that would result in overflow. (1e20).AsScaledInt64(1) would result +// in overflow because 1e19 is not representable as an int64. Note that setting a scale larger +// than the current value may result in loss of precision - i.e. (1e-6).AsScaledInt64(0) would +// return 1, because 0.000001 is rounded up to 1. +func (a int64Amount) AsScaledInt64(scale Scale) (result int64, ok bool) { + if a.scale < scale { + result, _ = negativeScaleInt64(a.value, scale-a.scale) + return result, true + } + return positiveScaleInt64(a.value, a.scale-scale) +} + +// AsDec returns an inf.Dec representation of this value. +func (a int64Amount) AsDec() *inf.Dec { + var base inf.Dec + base.SetUnscaled(a.value) + base.SetScale(inf.Scale(-a.scale)) + return &base +} + +// Cmp returns 0 if a and b are equal, 1 if a is greater than b, or -1 if a is less than b. +func (a int64Amount) Cmp(b int64Amount) int { + switch { + case a.scale == b.scale: + // compare only the unscaled portion + case a.scale > b.scale: + result, remainder, exact := divideByScaleInt64(b.value, a.scale-b.scale) + if !exact { + return a.AsDec().Cmp(b.AsDec()) + } + if result == a.value { + switch { + case remainder == 0: + return 0 + case remainder > 0: + return -1 + default: + return 1 + } + } + b.value = result + default: + result, remainder, exact := divideByScaleInt64(a.value, b.scale-a.scale) + if !exact { + return a.AsDec().Cmp(b.AsDec()) + } + if result == b.value { + switch { + case remainder == 0: + return 0 + case remainder > 0: + return 1 + default: + return -1 + } + } + a.value = result + } + + switch { + case a.value == b.value: + return 0 + case a.value < b.value: + return -1 + default: + return 1 + } +} + +// Add adds two int64Amounts together, matching scales. It will return false and not mutate +// a if overflow or underflow would result. +func (a *int64Amount) Add(b int64Amount) bool { + switch { + case b.value == 0: + return true + case a.value == 0: + a.value = b.value + a.scale = b.scale + return true + case a.scale == b.scale: + c, ok := int64Add(a.value, b.value) + if !ok { + return false + } + a.value = c + case a.scale > b.scale: + c, ok := positiveScaleInt64(a.value, a.scale-b.scale) + if !ok { + return false + } + c, ok = int64Add(c, b.value) + if !ok { + return false + } + a.scale = b.scale + a.value = c + default: + c, ok := positiveScaleInt64(b.value, b.scale-a.scale) + if !ok { + return false + } + c, ok = int64Add(a.value, c) + if !ok { + return false + } + a.value = c + } + return true +} + +// Sub removes the value of b from the current amount, or returns false if underflow would result. +func (a *int64Amount) Sub(b int64Amount) bool { + return a.Add(int64Amount{value: -b.value, scale: b.scale}) +} + +// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision +// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. +func (a int64Amount) AsScale(scale Scale) (int64Amount, bool) { + if a.scale >= scale { + return a, true + } + result, exact := negativeScaleInt64(a.value, scale-a.scale) + return int64Amount{value: result, scale: scale}, exact +} + +// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted +// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. +func (a int64Amount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + mantissa := a.value + exponent = int32(a.scale) + + amount, times := removeInt64Factors(mantissa, 10) + exponent += int32(times) + + // make sure exponent is a multiple of 3 + var ok bool + switch exponent % 3 { + case 1, -2: + amount, ok = int64MultiplyScale10(amount) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) + } + exponent = exponent - 1 + case 2, -1: + amount, ok = int64MultiplyScale100(amount) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) + } + exponent = exponent - 2 + } + return strconv.AppendInt(out, amount, 10), exponent +} + +// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would +// return []byte("2048"), 1. +func (a int64Amount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { + value, ok := a.AsScaledInt64(0) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBase1024Bytes(out) + } + amount, exponent := removeInt64Factors(value, 1024) + return strconv.AppendInt(out, amount, 10), exponent +} + +// infDecAmount implements common operations over an inf.Dec that are specific to the quantity +// representation. +type infDecAmount struct { + *inf.Dec +} + +// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision +// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. +func (a infDecAmount) AsScale(scale Scale) (infDecAmount, bool) { + tmp := &inf.Dec{} + tmp.Round(a.Dec, scale.infScale(), inf.RoundUp) + return infDecAmount{tmp}, tmp.Cmp(a.Dec) == 0 +} + +// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted +// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. +func (a infDecAmount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + mantissa := a.Dec.UnscaledBig() + exponent = int32(-a.Dec.Scale()) + amount := big.NewInt(0).Set(mantissa) + // move all factors of 10 into the exponent for easy reasoning + amount, times := removeBigIntFactors(amount, bigTen) + exponent += times + + // make sure exponent is a multiple of 3 + for exponent%3 != 0 { + amount.Mul(amount, bigTen) + exponent-- + } + + return append(out, amount.String()...), exponent +} + +// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would +// return []byte("2048"), 1. +func (a infDecAmount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { + tmp := &inf.Dec{} + tmp.Round(a.Dec, 0, inf.RoundUp) + amount, exponent := removeBigIntFactors(tmp.UnscaledBig(), big1024) + return append(out, amount.String()...), exponent +} diff --git a/vendor/github.com/appc/spec/schema/types/resource/math.go b/vendor/github.com/appc/spec/schema/types/resource/math.go new file mode 100644 index 00000000000..163aafa5db6 --- /dev/null +++ b/vendor/github.com/appc/spec/schema/types/resource/math.go @@ -0,0 +1,327 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "math/big" + + inf "gopkg.in/inf.v0" +) + +const ( + // maxInt64Factors is the highest value that will be checked when removing factors of 10 from an int64. + // It is also the maximum decimal digits that can be represented with an int64. + maxInt64Factors = 18 +) + +var ( + // Commonly needed big.Int values-- treat as read only! + bigTen = big.NewInt(10) + bigZero = big.NewInt(0) + bigOne = big.NewInt(1) + bigThousand = big.NewInt(1000) + big1024 = big.NewInt(1024) + + // Commonly needed inf.Dec values-- treat as read only! + decZero = inf.NewDec(0, 0) + decOne = inf.NewDec(1, 0) + decMinusOne = inf.NewDec(-1, 0) + decThousand = inf.NewDec(1000, 0) + dec1024 = inf.NewDec(1024, 0) + decMinus1024 = inf.NewDec(-1024, 0) + + // Largest (in magnitude) number allowed. + maxAllowed = infDecAmount{inf.NewDec((1<<63)-1, 0)} // == max int64 + + // The maximum value we can represent milli-units for. + // Compare with the return value of Quantity.Value() to + // see if it's safe to use Quantity.MilliValue(). + MaxMilliValue = int64(((1 << 63) - 1) / 1000) +) + +const mostNegative = -(mostPositive + 1) +const mostPositive = 1<<63 - 1 + +// int64Add returns a+b, or false if that would overflow int64. +func int64Add(a, b int64) (int64, bool) { + c := a + b + switch { + case a > 0 && b > 0: + if c < 0 { + return 0, false + } + case a < 0 && b < 0: + if c > 0 { + return 0, false + } + if a == mostNegative && b == mostNegative { + return 0, false + } + } + return c, true +} + +// int64Multiply returns a*b, or false if that would overflow or underflow int64. +func int64Multiply(a, b int64) (int64, bool) { + if a == 0 || b == 0 || a == 1 || b == 1 { + return a * b, true + } + if a == mostNegative || b == mostNegative { + return 0, false + } + c := a * b + return c, c/b == a +} + +// int64MultiplyScale returns a*b, assuming b is greater than one, or false if that would overflow or underflow int64. +// Use when b is known to be greater than one. +func int64MultiplyScale(a int64, b int64) (int64, bool) { + if a == 0 || a == 1 { + return a * b, true + } + if a == mostNegative && b != 1 { + return 0, false + } + c := a * b + return c, c/b == a +} + +// int64MultiplyScale10 multiplies a by 10, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 10) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale10(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 10, true + } + if a == mostNegative { + return 0, false + } + c := a * 10 + return c, c/10 == a +} + +// int64MultiplyScale100 multiplies a by 100, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 100) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale100(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 100, true + } + if a == mostNegative { + return 0, false + } + c := a * 100 + return c, c/100 == a +} + +// int64MultiplyScale1000 multiplies a by 1000, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 1000) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale1000(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 1000, true + } + if a == mostNegative { + return 0, false + } + c := a * 1000 + return c, c/1000 == a +} + +// positiveScaleInt64 multiplies base by 10^scale, returning false if the +// value overflows. Passing a negative scale is undefined. +func positiveScaleInt64(base int64, scale Scale) (int64, bool) { + switch scale { + case 0: + return base, true + case 1: + return int64MultiplyScale10(base) + case 2: + return int64MultiplyScale100(base) + case 3: + return int64MultiplyScale1000(base) + case 6: + return int64MultiplyScale(base, 1000000) + case 9: + return int64MultiplyScale(base, 1000000000) + default: + value := base + var ok bool + for i := Scale(0); i < scale; i++ { + if value, ok = int64MultiplyScale(value, 10); !ok { + return 0, false + } + } + return value, true + } +} + +// negativeScaleInt64 reduces base by the provided scale, rounding up, until the +// value is zero or the scale is reached. Passing a negative scale is undefined. +// The value returned, if not exact, is rounded away from zero. +func negativeScaleInt64(base int64, scale Scale) (result int64, exact bool) { + if scale == 0 { + return base, true + } + + value := base + var fraction bool + for i := Scale(0); i < scale; i++ { + if !fraction && value%10 != 0 { + fraction = true + } + value = value / 10 + if value == 0 { + if fraction { + if base > 0 { + return 1, false + } + return -1, false + } + return 0, true + } + } + if fraction { + if base > 0 { + value += 1 + } else { + value += -1 + } + } + return value, !fraction +} + +func pow10Int64(b int64) int64 { + switch b { + case 0: + return 1 + case 1: + return 10 + case 2: + return 100 + case 3: + return 1000 + case 4: + return 10000 + case 5: + return 100000 + case 6: + return 1000000 + case 7: + return 10000000 + case 8: + return 100000000 + case 9: + return 1000000000 + case 10: + return 10000000000 + case 11: + return 100000000000 + case 12: + return 1000000000000 + case 13: + return 10000000000000 + case 14: + return 100000000000000 + case 15: + return 1000000000000000 + case 16: + return 10000000000000000 + case 17: + return 100000000000000000 + case 18: + return 1000000000000000000 + default: + return 0 + } +} + +// powInt64 raises a to the bth power. Is not overflow aware. +func powInt64(a, b int64) int64 { + p := int64(1) + for b > 0 { + if b&1 != 0 { + p *= a + } + b >>= 1 + a *= a + } + return p +} + +// negativeScaleInt64 returns the result of dividing base by scale * 10 and the remainder, or +// false if no such division is possible. Dividing by negative scales is undefined. +func divideByScaleInt64(base int64, scale Scale) (result, remainder int64, exact bool) { + if scale == 0 { + return base, 0, true + } + // the max scale representable in base 10 in an int64 is 18 decimal places + if scale >= 18 { + return 0, base, false + } + divisor := pow10Int64(int64(scale)) + return base / divisor, base % divisor, true +} + +// removeInt64Factors divides in a loop; the return values have the property that +// value == result * base ^ scale +func removeInt64Factors(value int64, base int64) (result int64, times int32) { + times = 0 + result = value + negative := result < 0 + if negative { + result = -result + } + switch base { + // allow the compiler to optimize the common cases + case 10: + for result >= 10 && result%10 == 0 { + times++ + result = result / 10 + } + // allow the compiler to optimize the common cases + case 1024: + for result >= 1024 && result%1024 == 0 { + times++ + result = result / 1024 + } + default: + for result >= base && result%base == 0 { + times++ + result = result / base + } + } + if negative { + result = -result + } + return result, times +} + +// removeBigIntFactors divides in a loop; the return values have the property that +// d == result * factor ^ times +// d may be modified in place. +// If d == 0, then the return values will be (0, 0) +func removeBigIntFactors(d, factor *big.Int) (result *big.Int, times int32) { + q := big.NewInt(0) + m := big.NewInt(0) + for d.Cmp(bigZero) != 0 { + q.DivMod(d, factor, m) + if m.Cmp(bigZero) != 0 { + break + } + times++ + d, q = q, d + } + return d, times +} diff --git a/vendor/github.com/appc/spec/schema/types/resource/quantity.go b/vendor/github.com/appc/spec/schema/types/resource/quantity.go new file mode 100644 index 00000000000..96877b4c946 --- /dev/null +++ b/vendor/github.com/appc/spec/schema/types/resource/quantity.go @@ -0,0 +1,768 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "regexp" + "strconv" + "strings" + + flag "github.com/spf13/pflag" + + inf "gopkg.in/inf.v0" +) + +// Quantity is a fixed-point representation of a number. +// It provides convenient marshaling/unmarshaling in JSON and YAML, +// in addition to String() and Int64() accessors. +// +// The serialization format is: +// +// ::= +// (Note that may be empty, from the "" case in .) +// ::= 0 | 1 | ... | 9 +// ::= | +// ::= | . | . | . +// ::= "+" | "-" +// ::= | +// ::= | | +// ::= Ki | Mi | Gi | Ti | Pi | Ei +// (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html) +// ::= m | "" | k | M | G | T | P | E +// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) +// ::= "e" | "E" +// +// No matter which of the three exponent forms is used, no quantity may represent +// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal +// places. Numbers larger or more precise will be capped or rounded up. +// (E.g.: 0.1m will rounded up to 1m.) +// This may be extended in the future if we require larger or smaller quantities. +// +// When a Quantity is parsed from a string, it will remember the type of suffix +// it had, and will use the same type again when it is serialized. +// +// Before serializing, Quantity will be put in "canonical form". +// This means that Exponent/suffix will be adjusted up or down (with a +// corresponding increase or decrease in Mantissa) such that: +// a. No precision is lost +// b. No fractional digits will be emitted +// c. The exponent (or suffix) is as large as possible. +// The sign will be omitted unless the number is negative. +// +// Examples: +// 1.5 will be serialized as "1500m" +// 1.5Gi will be serialized as "1536Mi" +// +// NOTE: We reserve the right to amend this canonical format, perhaps to +// allow 1.5 to be canonical. +// TODO: Remove above disclaimer after all bikeshedding about format is over, +// or after March 2015. +// +// Note that the quantity will NEVER be internally represented by a +// floating point number. That is the whole point of this exercise. +// +// Non-canonical values will still parse as long as they are well formed, +// but will be re-emitted in their canonical form. (So always use canonical +// form, or don't diff.) +// +// This format is intended to make it difficult to use these numbers without +// writing some sort of special handling code in the hopes that that will +// cause implementors to also use a fixed point implementation. +// +// +gencopy=false +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +type Quantity struct { + // i is the quantity in int64 scaled form, if d.Dec == nil + i int64Amount + // d is the quantity in inf.Dec form if d.Dec != nil + d infDecAmount + // s is the generated value of this quantity to avoid recalculation + s string + + // Change Format at will. See the comment for Canonicalize for + // more details. + Format +} + +// CanonicalValue allows a quantity amount to be converted to a string. +type CanonicalValue interface { + // AsCanonicalBytes returns a byte array representing the string representation + // of the value mantissa and an int32 representing its exponent in base-10. Callers may + // pass a byte slice to the method to avoid allocations. + AsCanonicalBytes(out []byte) ([]byte, int32) + // AsCanonicalBase1024Bytes returns a byte array representing the string representation + // of the value mantissa and an int32 representing its exponent in base-1024. Callers + // may pass a byte slice to the method to avoid allocations. + AsCanonicalBase1024Bytes(out []byte) ([]byte, int32) +} + +// Format lists the three possible formattings of a quantity. +type Format string + +const ( + DecimalExponent = Format("DecimalExponent") // e.g., 12e6 + BinarySI = Format("BinarySI") // e.g., 12Mi (12 * 2^20) + DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6) +) + +// MustParse turns the given string into a quantity or panics; for tests +// or others cases where you know the string is valid. +func MustParse(str string) Quantity { + q, err := ParseQuantity(str) + if err != nil { + panic(fmt.Errorf("cannot parse '%v': %v", str, err)) + } + return q +} + +const ( + // splitREString is used to separate a number from its suffix; as such, + // this is overly permissive, but that's OK-- it will be checked later. + splitREString = "^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$" +) + +var ( + // splitRE is used to get the various parts of a number. + splitRE = regexp.MustCompile(splitREString) + + // Errors that could happen while parsing a string. + ErrFormatWrong = errors.New("quantities must match the regular expression '" + splitREString + "'") + ErrNumeric = errors.New("unable to parse numeric part of quantity") + ErrSuffix = errors.New("unable to parse quantity's suffix") +) + +// parseQuantityString is a fast scanner for quantity values. +func parseQuantityString(str string) (positive bool, value, num, denom, suffix string, err error) { + positive = true + pos := 0 + end := len(str) + + // handle leading sign + if pos < end { + switch str[0] { + case '-': + positive = false + pos++ + case '+': + pos++ + } + } + + // strip leading zeros +Zeroes: + for i := pos; ; i++ { + if i >= end { + num = "0" + value = num + return + } + switch str[i] { + case '0': + pos++ + default: + break Zeroes + } + } + + // extract the numerator +Num: + for i := pos; ; i++ { + if i >= end { + num = str[pos:end] + value = str[0:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + num = str[pos:i] + pos = i + break Num + } + } + + // if we stripped all numerator positions, always return 0 + if len(num) == 0 { + num = "0" + } + + // handle a denominator + if pos < end && str[pos] == '.' { + pos++ + Denom: + for i := pos; ; i++ { + if i >= end { + denom = str[pos:end] + value = str[0:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + denom = str[pos:i] + pos = i + break Denom + } + } + // TODO: we currently allow 1.G, but we may not want to in the future. + // if len(denom) == 0 { + // err = ErrFormatWrong + // return + // } + } + value = str[0:pos] + + // grab the elements of the suffix + suffixStart := pos + for i := pos; ; i++ { + if i >= end { + suffix = str[suffixStart:end] + return + } + if !strings.ContainsAny(str[i:i+1], "eEinumkKMGTP") { + pos = i + break + } + } + if pos < end { + switch str[pos] { + case '-', '+': + pos++ + } + } +Suffix: + for i := pos; ; i++ { + if i >= end { + suffix = str[suffixStart:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + break Suffix + } + } + // we encountered a non decimal in the Suffix loop, but the last character + // was not a valid exponent + err = ErrFormatWrong + return +} + +// ParseQuantity turns str into a Quantity, or returns an error. +func ParseQuantity(str string) (Quantity, error) { + if len(str) == 0 { + return Quantity{}, ErrFormatWrong + } + if str == "0" { + return Quantity{Format: DecimalSI, s: str}, nil + } + + positive, value, num, denom, suf, err := parseQuantityString(str) + if err != nil { + return Quantity{}, err + } + + base, exponent, format, ok := quantitySuffixer.interpret(suffix(suf)) + if !ok { + return Quantity{}, ErrSuffix + } + + precision := int32(0) + scale := int32(0) + mantissa := int64(1) + switch format { + case DecimalExponent, DecimalSI: + scale = exponent + precision = maxInt64Factors - int32(len(num)+len(denom)) + case BinarySI: + scale = 0 + switch { + case exponent >= 0 && len(denom) == 0: + // only handle positive binary numbers with the fast path + mantissa = int64(int64(mantissa) << uint64(exponent)) + // 1Mi (2^20) has ~6 digits of decimal precision, so exponent*3/10 -1 is roughly the precision + precision = 15 - int32(len(num)) - int32(float32(exponent)*3/10) - 1 + default: + precision = -1 + } + } + + if precision >= 0 { + // if we have a denominator, shift the entire value to the left by the number of places in the + // denominator + scale -= int32(len(denom)) + if scale >= int32(Nano) { + shifted := num + denom + + var value int64 + value, err := strconv.ParseInt(shifted, 10, 64) + if err != nil { + return Quantity{}, ErrNumeric + } + if result, ok := int64Multiply(value, int64(mantissa)); ok { + if !positive { + result = -result + } + // if the number is in canonical form, reuse the string + switch format { + case BinarySI: + if exponent%10 == 0 && (value&0x07 != 0) { + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil + } + default: + if scale%3 == 0 && !strings.HasSuffix(shifted, "000") && shifted[0] != '0' { + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil + } + } + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format}, nil + } + } + } + + amount := new(inf.Dec) + if _, ok := amount.SetString(value); !ok { + return Quantity{}, ErrNumeric + } + + // So that no one but us has to think about suffixes, remove it. + if base == 10 { + amount.SetScale(amount.Scale() + Scale(exponent).infScale()) + } else if base == 2 { + // numericSuffix = 2 ** exponent + numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent)) + ub := amount.UnscaledBig() + amount.SetUnscaledBig(ub.Mul(ub, numericSuffix)) + } + + // Cap at min/max bounds. + sign := amount.Sign() + if sign == -1 { + amount.Neg(amount) + } + + // This rounds non-zero values up to the minimum representable value, under the theory that + // if you want some resources, you should get some resources, even if you asked for way too small + // of an amount. Arguably, this should be inf.RoundHalfUp (normal rounding), but that would have + // the side effect of rounding values < .5n to zero. + if v, ok := amount.Unscaled(); v != int64(0) || !ok { + amount.Round(amount, Nano.infScale(), inf.RoundUp) + } + + // The max is just a simple cap. + // TODO: this prevents accumulating quantities greater than int64, for instance quota across a cluster + if format == BinarySI && amount.Cmp(maxAllowed.Dec) > 0 { + amount.Set(maxAllowed.Dec) + } + + if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 { + // This avoids rounding and hopefully confusion, too. + format = DecimalSI + } + if sign == -1 { + amount.Neg(amount) + } + + return Quantity{d: infDecAmount{amount}, Format: format}, nil +} + +// CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity). +// +// Note about BinarySI: +// * If q.Format is set to BinarySI and q.Amount represents a non-zero value between +// -1 and +1, it will be emitted as if q.Format were DecimalSI. +// * Otherwise, if q.Format is set to BinarySI, frational parts of q.Amount will be +// rounded up. (1.1i becomes 2i.) +func (q *Quantity) CanonicalizeBytes(out []byte) (result, suffix []byte) { + if q.IsZero() { + return zeroBytes, nil + } + + var rounded CanonicalValue + format := q.Format + switch format { + case DecimalExponent, DecimalSI: + case BinarySI: + if q.CmpInt64(-1024) > 0 && q.CmpInt64(1024) < 0 { + // This avoids rounding and hopefully confusion, too. + format = DecimalSI + } else { + var exact bool + if rounded, exact = q.AsScale(0); !exact { + // Don't lose precision-- show as DecimalSI + format = DecimalSI + } + } + default: + format = DecimalExponent + } + + // TODO: If BinarySI formatting is requested but would cause rounding, upgrade to + // one of the other formats. + switch format { + case DecimalExponent, DecimalSI: + number, exponent := q.AsCanonicalBytes(out) + suffix, _ := quantitySuffixer.constructBytes(10, exponent, format) + return number, suffix + default: + // format must be BinarySI + number, exponent := rounded.AsCanonicalBase1024Bytes(out) + suffix, _ := quantitySuffixer.constructBytes(2, exponent*10, format) + return number, suffix + } +} + +// AsInt64 returns a representation of the current value as an int64 if a fast conversion +// is possible. If false is returned, callers must use the inf.Dec form of this quantity. +func (q *Quantity) AsInt64() (int64, bool) { + if q.d.Dec != nil { + return 0, false + } + return q.i.AsInt64() +} + +// ToDec promotes the quantity in place to use an inf.Dec representation and returns itself. +func (q *Quantity) ToDec() *Quantity { + if q.d.Dec == nil { + q.d.Dec = q.i.AsDec() + q.i = int64Amount{} + } + return q +} + +// AsDec returns the quantity as represented by a scaled inf.Dec. +func (q *Quantity) AsDec() *inf.Dec { + if q.d.Dec != nil { + return q.d.Dec + } + q.d.Dec = q.i.AsDec() + q.i = int64Amount{} + return q.d.Dec +} + +// AsCanonicalBytes returns the canonical byte representation of this quantity as a mantissa +// and base 10 exponent. The out byte slice may be passed to the method to avoid an extra +// allocation. +func (q *Quantity) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + if q.d.Dec != nil { + return q.d.AsCanonicalBytes(out) + } + return q.i.AsCanonicalBytes(out) +} + +// IsZero returns true if the quantity is equal to zero. +func (q *Quantity) IsZero() bool { + if q.d.Dec != nil { + return q.d.Dec.Sign() == 0 + } + return q.i.value == 0 +} + +// Sign returns 0 if the quantity is zero, -1 if the quantity is less than zero, or 1 if the +// quantity is greater than zero. +func (q *Quantity) Sign() int { + if q.d.Dec != nil { + return q.d.Dec.Sign() + } + return q.i.Sign() +} + +// AsScaled returns the current value, rounded up to the provided scale, and returns +// false if the scale resulted in a loss of precision. +func (q *Quantity) AsScale(scale Scale) (CanonicalValue, bool) { + if q.d.Dec != nil { + return q.d.AsScale(scale) + } + return q.i.AsScale(scale) +} + +// RoundUp updates the quantity to the provided scale, ensuring that the value is at +// least 1. False is returned if the rounding operation resulted in a loss of precision. +// Negative numbers are rounded away from zero (-9 scale 1 rounds to -10). +func (q *Quantity) RoundUp(scale Scale) bool { + if q.d.Dec != nil { + q.s = "" + d, exact := q.d.AsScale(scale) + q.d = d + return exact + } + // avoid clearing the string value if we have already calculated it + if q.i.scale >= scale { + return true + } + q.s = "" + i, exact := q.i.AsScale(scale) + q.i = i + return exact +} + +// Add adds the provide y quantity to the current value. If the current value is zero, +// the format of the quantity will be updated to the format of y. +func (q *Quantity) Add(y Quantity) { + q.s = "" + if q.d.Dec == nil && y.d.Dec == nil { + if q.i.value == 0 { + q.Format = y.Format + } + if q.i.Add(y.i) { + return + } + } else if q.IsZero() { + q.Format = y.Format + } + q.ToDec().d.Dec.Add(q.d.Dec, y.AsDec()) +} + +// Sub subtracts the provided quantity from the current value in place. If the current +// value is zero, the format of the quantity will be updated to the format of y. +func (q *Quantity) Sub(y Quantity) { + q.s = "" + if q.IsZero() { + q.Format = y.Format + } + if q.d.Dec == nil && y.d.Dec == nil && q.i.Sub(y.i) { + return + } + q.ToDec().d.Dec.Sub(q.d.Dec, y.AsDec()) +} + +// Cmp returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the +// quantity is greater than y. +func (q *Quantity) Cmp(y Quantity) int { + if q.d.Dec == nil && y.d.Dec == nil { + return q.i.Cmp(y.i) + } + return q.AsDec().Cmp(y.AsDec()) +} + +// CmpInt64 returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the +// quantity is greater than y. +func (q *Quantity) CmpInt64(y int64) int { + if q.d.Dec != nil { + return q.d.Dec.Cmp(inf.NewDec(y, inf.Scale(0))) + } + return q.i.Cmp(int64Amount{value: y}) +} + +// Neg sets quantity to be the negative value of itself. +func (q *Quantity) Neg() { + q.s = "" + if q.d.Dec == nil { + q.i.value = -q.i.value + return + } + q.d.Dec.Neg(q.d.Dec) +} + +// int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation +// of most Quantity values. +const int64QuantityExpectedBytes = 18 + +// String formats the Quantity as a string, caching the result if not calculated. +// String is an expensive operation and caching this result significantly reduces the cost of +// normal parse / marshal operations on Quantity. +func (q *Quantity) String() string { + if len(q.s) == 0 { + result := make([]byte, 0, int64QuantityExpectedBytes) + number, suffix := q.CanonicalizeBytes(result) + number = append(number, suffix...) + q.s = string(number) + } + return q.s +} + +// MarshalJSON implements the json.Marshaller interface. +func (q Quantity) MarshalJSON() ([]byte, error) { + if len(q.s) > 0 { + out := make([]byte, len(q.s)+2) + out[0], out[len(out)-1] = '"', '"' + copy(out[1:], q.s) + return out, nil + } + result := make([]byte, int64QuantityExpectedBytes, int64QuantityExpectedBytes) + result[0] = '"' + number, suffix := q.CanonicalizeBytes(result[1:1]) + // if the same slice was returned to us that we passed in, avoid another allocation by copying number into + // the source slice and returning that + if len(number) > 0 && &number[0] == &result[1] && (len(number)+len(suffix)+2) <= int64QuantityExpectedBytes { + number = append(number, suffix...) + number = append(number, '"') + return result[:1+len(number)], nil + } + // if CanonicalizeBytes needed more space than our slice provided, we may need to allocate again so use + // append + result = result[:1] + result = append(result, number...) + result = append(result, suffix...) + result = append(result, '"') + return result, nil +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +// TODO: Remove support for leading/trailing whitespace +func (q *Quantity) UnmarshalJSON(value []byte) error { + l := len(value) + if l == 4 && bytes.Equal(value, []byte("null")) { + q.d.Dec = nil + q.i = int64Amount{} + return nil + } + if l >= 2 && value[0] == '"' && value[l-1] == '"' { + value = value[1 : l-1] + } + + parsed, err := ParseQuantity(strings.TrimSpace(string(value))) + if err != nil { + return err + } + + // This copy is safe because parsed will not be referred to again. + *q = parsed + return nil +} + +// NewQuantity returns a new Quantity representing the given +// value in the given format. +func NewQuantity(value int64, format Format) *Quantity { + return &Quantity{ + i: int64Amount{value: value}, + Format: format, + } +} + +// NewMilliQuantity returns a new Quantity representing the given +// value * 1/1000 in the given format. Note that BinarySI formatting +// will round fractional values, and will be changed to DecimalSI for +// values x where (-1 < x < 1) && (x != 0). +func NewMilliQuantity(value int64, format Format) *Quantity { + return &Quantity{ + i: int64Amount{value: value, scale: -3}, + Format: format, + } +} + +// NewScaledQuantity returns a new Quantity representing the given +// value * 10^scale in DecimalSI format. +func NewScaledQuantity(value int64, scale Scale) *Quantity { + return &Quantity{ + i: int64Amount{value: value, scale: scale}, + Format: DecimalSI, + } +} + +// Value returns the value of q; any fractional part will be lost. +func (q *Quantity) Value() int64 { + return q.ScaledValue(0) +} + +// MilliValue returns the value of ceil(q * 1000); this could overflow an int64; +// if that's a concern, call Value() first to verify the number is small enough. +func (q *Quantity) MilliValue() int64 { + return q.ScaledValue(Milli) +} + +// ScaledValue returns the value of ceil(q * 10^scale); this could overflow an int64. +// To detect overflow, call Value() first and verify the expected magnitude. +func (q *Quantity) ScaledValue(scale Scale) int64 { + if q.d.Dec == nil { + i, _ := q.i.AsScaledInt64(scale) + return i + } + dec := q.d.Dec + return scaledValue(dec.UnscaledBig(), int(dec.Scale()), int(scale.infScale())) +} + +// Set sets q's value to be value. +func (q *Quantity) Set(value int64) { + q.SetScaled(value, 0) +} + +// SetMilli sets q's value to be value * 1/1000. +func (q *Quantity) SetMilli(value int64) { + q.SetScaled(value, Milli) +} + +// SetScaled sets q's value to be value * 10^scale +func (q *Quantity) SetScaled(value int64, scale Scale) { + q.s = "" + q.d.Dec = nil + q.i = int64Amount{value: value, scale: scale} +} + +// Copy is a convenience function that makes a deep copy for you. Non-deep +// copies of quantities share pointers and you will regret that. +func (q *Quantity) Copy() *Quantity { + if q.d.Dec == nil { + return &Quantity{ + s: q.s, + i: q.i, + Format: q.Format, + } + } + tmp := &inf.Dec{} + return &Quantity{ + s: q.s, + d: infDecAmount{tmp.Set(q.d.Dec)}, + Format: q.Format, + } +} + +// qFlag is a helper type for the Flag function +type qFlag struct { + dest *Quantity +} + +// Sets the value of the internal Quantity. (used by flag & pflag) +func (qf qFlag) Set(val string) error { + q, err := ParseQuantity(val) + if err != nil { + return err + } + // This copy is OK because q will not be referenced again. + *qf.dest = q + return nil +} + +// Converts the value of the internal Quantity to a string. (used by flag & pflag) +func (qf qFlag) String() string { + return qf.dest.String() +} + +// States the type of flag this is (Quantity). (used by pflag) +func (qf qFlag) Type() string { + return "quantity" +} + +// QuantityFlag is a helper that makes a quantity flag (using standard flag package). +// Will panic if defaultValue is not a valid quantity. +func QuantityFlag(flagName, defaultValue, description string) *Quantity { + q := MustParse(defaultValue) + flag.Var(NewQuantityFlagValue(&q), flagName, description) + return &q +} + +// NewQuantityFlagValue returns an object that can be used to back a flag, +// pointing at the given Quantity variable. +func NewQuantityFlagValue(q *Quantity) flag.Value { + return qFlag{q} +} diff --git a/vendor/github.com/appc/spec/schema/types/resource/scale_int.go b/vendor/github.com/appc/spec/schema/types/resource/scale_int.go new file mode 100644 index 00000000000..173de1a2171 --- /dev/null +++ b/vendor/github.com/appc/spec/schema/types/resource/scale_int.go @@ -0,0 +1,95 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "math" + "math/big" + "sync" +) + +var ( + // A sync pool to reduce allocation. + intPool sync.Pool + maxInt64 = big.NewInt(math.MaxInt64) +) + +func init() { + intPool.New = func() interface{} { + return &big.Int{} + } +} + +// scaledValue scales given unscaled value from scale to new Scale and returns +// an int64. It ALWAYS rounds up the result when scale down. The final result might +// overflow. +// +// scale, newScale represents the scale of the unscaled decimal. +// The mathematical value of the decimal is unscaled * 10**(-scale). +func scaledValue(unscaled *big.Int, scale, newScale int) int64 { + dif := scale - newScale + if dif == 0 { + return unscaled.Int64() + } + + // Handle scale up + // This is an easy case, we do not need to care about rounding and overflow. + // If any intermediate operation causes overflow, the result will overflow. + if dif < 0 { + return unscaled.Int64() * int64(math.Pow10(-dif)) + } + + // Handle scale down + // We have to be careful about the intermediate operations. + + // fast path when unscaled < max.Int64 and exp(10,dif) < max.Int64 + const log10MaxInt64 = 19 + if unscaled.Cmp(maxInt64) < 0 && dif < log10MaxInt64 { + divide := int64(math.Pow10(dif)) + result := unscaled.Int64() / divide + mod := unscaled.Int64() % divide + if mod != 0 { + return result + 1 + } + return result + } + + // We should only convert back to int64 when getting the result. + divisor := intPool.Get().(*big.Int) + exp := intPool.Get().(*big.Int) + result := intPool.Get().(*big.Int) + defer func() { + intPool.Put(divisor) + intPool.Put(exp) + intPool.Put(result) + }() + + // divisor = 10^(dif) + // TODO: create loop up table if exp costs too much. + divisor.Exp(bigTen, exp.SetInt64(int64(dif)), nil) + // reuse exp + remainder := exp + + // result = unscaled / divisor + // remainder = unscaled % divisor + result.DivMod(unscaled, divisor, remainder) + if remainder.Sign() != 0 { + return result.Int64() + 1 + } + + return result.Int64() +} diff --git a/vendor/github.com/appc/spec/schema/types/resource/suffix.go b/vendor/github.com/appc/spec/schema/types/resource/suffix.go new file mode 100644 index 00000000000..0aa2ce2bf60 --- /dev/null +++ b/vendor/github.com/appc/spec/schema/types/resource/suffix.go @@ -0,0 +1,198 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "strconv" +) + +type suffix string + +// suffixer can interpret and construct suffixes. +type suffixer interface { + interpret(suffix) (base, exponent int32, fmt Format, ok bool) + construct(base, exponent int32, fmt Format) (s suffix, ok bool) + constructBytes(base, exponent int32, fmt Format) (s []byte, ok bool) +} + +// quantitySuffixer handles suffixes for all three formats that quantity +// can handle. +var quantitySuffixer = newSuffixer() + +type bePair struct { + base, exponent int32 +} + +type listSuffixer struct { + suffixToBE map[suffix]bePair + beToSuffix map[bePair]suffix + beToSuffixBytes map[bePair][]byte +} + +func (ls *listSuffixer) addSuffix(s suffix, pair bePair) { + if ls.suffixToBE == nil { + ls.suffixToBE = map[suffix]bePair{} + } + if ls.beToSuffix == nil { + ls.beToSuffix = map[bePair]suffix{} + } + if ls.beToSuffixBytes == nil { + ls.beToSuffixBytes = map[bePair][]byte{} + } + ls.suffixToBE[s] = pair + ls.beToSuffix[pair] = s + ls.beToSuffixBytes[pair] = []byte(s) +} + +func (ls *listSuffixer) lookup(s suffix) (base, exponent int32, ok bool) { + pair, ok := ls.suffixToBE[s] + if !ok { + return 0, 0, false + } + return pair.base, pair.exponent, true +} + +func (ls *listSuffixer) construct(base, exponent int32) (s suffix, ok bool) { + s, ok = ls.beToSuffix[bePair{base, exponent}] + return +} + +func (ls *listSuffixer) constructBytes(base, exponent int32) (s []byte, ok bool) { + s, ok = ls.beToSuffixBytes[bePair{base, exponent}] + return +} + +type suffixHandler struct { + decSuffixes listSuffixer + binSuffixes listSuffixer +} + +type fastLookup struct { + *suffixHandler +} + +func (l fastLookup) interpret(s suffix) (base, exponent int32, format Format, ok bool) { + switch s { + case "": + return 10, 0, DecimalSI, true + case "n": + return 10, -9, DecimalSI, true + case "u": + return 10, -6, DecimalSI, true + case "m": + return 10, -3, DecimalSI, true + case "k": + return 10, 3, DecimalSI, true + case "M": + return 10, 6, DecimalSI, true + case "G": + return 10, 9, DecimalSI, true + } + return l.suffixHandler.interpret(s) +} + +func newSuffixer() suffixer { + sh := &suffixHandler{} + + // IMPORTANT: if you change this section you must change fastLookup + + sh.binSuffixes.addSuffix("Ki", bePair{2, 10}) + sh.binSuffixes.addSuffix("Mi", bePair{2, 20}) + sh.binSuffixes.addSuffix("Gi", bePair{2, 30}) + sh.binSuffixes.addSuffix("Ti", bePair{2, 40}) + sh.binSuffixes.addSuffix("Pi", bePair{2, 50}) + sh.binSuffixes.addSuffix("Ei", bePair{2, 60}) + // Don't emit an error when trying to produce + // a suffix for 2^0. + sh.decSuffixes.addSuffix("", bePair{2, 0}) + + sh.decSuffixes.addSuffix("n", bePair{10, -9}) + sh.decSuffixes.addSuffix("u", bePair{10, -6}) + sh.decSuffixes.addSuffix("m", bePair{10, -3}) + sh.decSuffixes.addSuffix("", bePair{10, 0}) + sh.decSuffixes.addSuffix("k", bePair{10, 3}) + sh.decSuffixes.addSuffix("M", bePair{10, 6}) + sh.decSuffixes.addSuffix("G", bePair{10, 9}) + sh.decSuffixes.addSuffix("T", bePair{10, 12}) + sh.decSuffixes.addSuffix("P", bePair{10, 15}) + sh.decSuffixes.addSuffix("E", bePair{10, 18}) + + return fastLookup{sh} +} + +func (sh *suffixHandler) construct(base, exponent int32, fmt Format) (s suffix, ok bool) { + switch fmt { + case DecimalSI: + return sh.decSuffixes.construct(base, exponent) + case BinarySI: + return sh.binSuffixes.construct(base, exponent) + case DecimalExponent: + if base != 10 { + return "", false + } + if exponent == 0 { + return "", true + } + return suffix("e" + strconv.FormatInt(int64(exponent), 10)), true + } + return "", false +} + +func (sh *suffixHandler) constructBytes(base, exponent int32, format Format) (s []byte, ok bool) { + switch format { + case DecimalSI: + return sh.decSuffixes.constructBytes(base, exponent) + case BinarySI: + return sh.binSuffixes.constructBytes(base, exponent) + case DecimalExponent: + if base != 10 { + return nil, false + } + if exponent == 0 { + return nil, true + } + result := make([]byte, 8, 8) + result[0] = 'e' + number := strconv.AppendInt(result[1:1], int64(exponent), 10) + if &result[1] == &number[0] { + return result[:1+len(number)], true + } + result = append(result[:1], number...) + return result, true + } + return nil, false +} + +func (sh *suffixHandler) interpret(suffix suffix) (base, exponent int32, fmt Format, ok bool) { + // Try lookup tables first + if b, e, ok := sh.decSuffixes.lookup(suffix); ok { + return b, e, DecimalSI, true + } + if b, e, ok := sh.binSuffixes.lookup(suffix); ok { + return b, e, BinarySI, true + } + + if len(suffix) > 1 && (suffix[0] == 'E' || suffix[0] == 'e') { + parsed, err := strconv.ParseInt(string(suffix[1:]), 10, 64) + if err != nil { + return 0, 0, DecimalExponent, false + } + return 10, int32(parsed), DecimalExponent, true + } + + return 0, 0, DecimalExponent, false +} diff --git a/vendor/github.com/appc/spec/schema/types/user_annotations.go b/vendor/github.com/appc/spec/schema/types/user_annotations.go new file mode 100644 index 00000000000..63ba434ea5d --- /dev/null +++ b/vendor/github.com/appc/spec/schema/types/user_annotations.go @@ -0,0 +1,18 @@ +// Copyright 2016 The appc Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +// UserAnnotations are arbitrary key-value pairs, to be supplied and interpreted by the user +type UserAnnotations map[string]string diff --git a/vendor/github.com/appc/spec/schema/types/user_labels.go b/vendor/github.com/appc/spec/schema/types/user_labels.go new file mode 100644 index 00000000000..5ed927c9e8e --- /dev/null +++ b/vendor/github.com/appc/spec/schema/types/user_labels.go @@ -0,0 +1,18 @@ +// Copyright 2015 The appc Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +// UserLabels are arbitrary key-value pairs, to be supplied and interpreted by the user +type UserLabels map[string]string diff --git a/vendor/github.com/appc/spec/schema/types/volume.go b/vendor/github.com/appc/spec/schema/types/volume.go index c5ae59180f1..0986a61d25f 100644 --- a/vendor/github.com/appc/spec/schema/types/volume.go +++ b/vendor/github.com/appc/spec/schema/types/volume.go @@ -40,8 +40,9 @@ type Volume struct { // currently used only by "host" // TODO(jonboulle): factor out? - Source string `json:"source,omitempty"` - ReadOnly *bool `json:"readOnly,omitempty"` + Source string `json:"source,omitempty"` + ReadOnly *bool `json:"readOnly,omitempty"` + Recursive *bool `json:"recursive,omitempty"` // currently used only by "empty" Mode *string `json:"mode,omitempty"` @@ -128,6 +129,10 @@ func (v Volume) String() string { s = append(s, ",readOnly=") s = append(s, strconv.FormatBool(*v.ReadOnly)) } + if v.Recursive != nil { + s = append(s, ",recursive=") + s = append(s, strconv.FormatBool(*v.Recursive)) + } switch v.Kind { case "empty": if *v.Mode != emptyVolumeDefaultMode { @@ -149,10 +154,8 @@ func (v Volume) String() string { // VolumeFromString takes a command line volume parameter and returns a volume // // Example volume parameters: -// database,kind=host,source=/tmp,readOnly=true +// database,kind=host,source=/tmp,readOnly=true,recursive=true func VolumeFromString(vp string) (*Volume, error) { - var vol Volume - vp = "name=" + vp vpQuery, err := common.MakeQueryString(vp) if err != nil { @@ -163,7 +166,12 @@ func VolumeFromString(vp string) (*Volume, error) { if err != nil { return nil, err } - for key, val := range v { + return VolumeFromParams(v) +} + +func VolumeFromParams(params map[string][]string) (*Volume, error) { + var vol Volume + for key, val := range params { val := val if len(val) > 1 { return nil, fmt.Errorf("label %s with multiple values %q", key, val) @@ -186,6 +194,12 @@ func VolumeFromString(vp string) (*Volume, error) { return nil, err } vol.ReadOnly = &ro + case "recursive": + rec, err := strconv.ParseBool(val[0]) + if err != nil { + return nil, err + } + vol.Recursive = &rec case "mode": vol.Mode = &val[0] case "uid": @@ -207,8 +221,7 @@ func VolumeFromString(vp string) (*Volume, error) { maybeSetDefaults(&vol) - err = vol.assertValid() - if err != nil { + if err := vol.assertValid(); err != nil { return nil, err } diff --git a/vendor/github.com/appc/spec/schema/version.go b/vendor/github.com/appc/spec/schema/version.go index e8884e970de..1fa0802949b 100644 --- a/vendor/github.com/appc/spec/schema/version.go +++ b/vendor/github.com/appc/spec/schema/version.go @@ -22,7 +22,7 @@ const ( // version represents the canonical version of the appc spec and tooling. // For now, the schema and tooling is coupled with the spec itself, so // this must be kept in sync with the VERSION file in the root of the repo. - version string = "0.8.1+git" + version string = "0.8.9+git" ) var (