diff options
author | Matthew Heon <matthew.heon@gmail.com> | 2017-11-01 11:24:59 -0400 |
---|---|---|
committer | Matthew Heon <matthew.heon@gmail.com> | 2017-11-01 11:24:59 -0400 |
commit | a031b83a09a8628435317a03f199cdc18b78262f (patch) | |
tree | bc017a96769ce6de33745b8b0b1304ccf38e9df0 /vendor/k8s.io/kubernetes/pkg/util | |
parent | 2b74391cd5281f6fdf391ff8ad50fd1490f6bf89 (diff) | |
download | podman-a031b83a09a8628435317a03f199cdc18b78262f.tar.gz podman-a031b83a09a8628435317a03f199cdc18b78262f.tar.bz2 podman-a031b83a09a8628435317a03f199cdc18b78262f.zip |
Initial checkin from CRI-O repo
Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
Diffstat (limited to 'vendor/k8s.io/kubernetes/pkg/util')
42 files changed, 4713 insertions, 0 deletions
diff --git a/vendor/k8s.io/kubernetes/pkg/util/async/bounded_frequency_runner.go b/vendor/k8s.io/kubernetes/pkg/util/async/bounded_frequency_runner.go new file mode 100644 index 000000000..da6fc2a4f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/async/bounded_frequency_runner.go @@ -0,0 +1,239 @@ +/* +Copyright 2017 The Kubernetes 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 async + +import ( + "fmt" + "sync" + "time" + + "k8s.io/client-go/util/flowcontrol" + + "github.com/golang/glog" +) + +// BoundedFrequencyRunner manages runs of a user-provided function. +// See NewBoundedFrequencyRunner for examples. +type BoundedFrequencyRunner struct { + name string // the name of this instance + minInterval time.Duration // the min time between runs, modulo bursts + maxInterval time.Duration // the max time between runs + + run chan struct{} // try an async run + + mu sync.Mutex // guards runs of fn and all mutations + fn func() // function to run + lastRun time.Time // time of last run + timer timer // timer for deferred runs + limiter rateLimiter // rate limiter for on-demand runs +} + +// designed so that flowcontrol.RateLimiter satisfies +type rateLimiter interface { + TryAccept() bool + Stop() +} + +type nullLimiter struct{} + +func (nullLimiter) TryAccept() bool { + return true +} + +func (nullLimiter) Stop() {} + +var _ rateLimiter = nullLimiter{} + +// for testing +type timer interface { + // C returns the timer's selectable channel. + C() <-chan time.Time + + // See time.Timer.Reset. + Reset(d time.Duration) bool + + // See time.Timer.Stop. + Stop() bool + + // See time.Now. + Now() time.Time + + // See time.Since. + Since(t time.Time) time.Duration + + // See time.Sleep. + Sleep(d time.Duration) +} + +// implement our timer in terms of std time.Timer. +type realTimer struct { + *time.Timer +} + +func (rt realTimer) C() <-chan time.Time { + return rt.Timer.C +} + +func (rt realTimer) Now() time.Time { + return time.Now() +} + +func (rt realTimer) Since(t time.Time) time.Duration { + return time.Since(t) +} + +func (rt realTimer) Sleep(d time.Duration) { + time.Sleep(d) +} + +var _ timer = realTimer{} + +// NewBoundedFrequencyRunner creates a new BoundedFrequencyRunner instance, +// which will manage runs of the specified function. +// +// All runs will be async to the caller of BoundedFrequencyRunner.Run, but +// multiple runs are serialized. If the function needs to hold locks, it must +// take them internally. +// +// Runs of the funtion will have at least minInterval between them (from +// completion to next start), except that up to bursts may be allowed. Burst +// runs are "accumulated" over time, one per minInterval up to burstRuns total. +// This can be used, for example, to mitigate the impact of expensive operations +// being called in response to user-initiated operations. Run requests that +// would violate the minInterval are coallesced and run at the next opportunity. +// +// The function will be run at least once per maxInterval. For example, this can +// force periodic refreshes of state in the absence of anyone calling Run. +// +// Examples: +// +// NewBoundedFrequencyRunner("name", fn, time.Second, 5*time.Second, 1) +// - fn will have at least 1 second between runs +// - fn will have no more than 5 seconds between runs +// +// NewBoundedFrequencyRunner("name", fn, 3*time.Second, 10*time.Second, 3) +// - fn will have at least 3 seconds between runs, with up to 3 burst runs +// - fn will have no more than 10 seconds between runs +// +// The maxInterval must be greater than or equal to the minInterval, If the +// caller passes a maxInterval less than minInterval, this function will panic. +func NewBoundedFrequencyRunner(name string, fn func(), minInterval, maxInterval time.Duration, burstRuns int) *BoundedFrequencyRunner { + timer := realTimer{Timer: time.NewTimer(0)} // will tick immediately + <-timer.C() // consume the first tick + return construct(name, fn, minInterval, maxInterval, burstRuns, timer) +} + +// Make an instance with dependencies injected. +func construct(name string, fn func(), minInterval, maxInterval time.Duration, burstRuns int, timer timer) *BoundedFrequencyRunner { + if maxInterval < minInterval { + panic(fmt.Sprintf("%s: maxInterval (%v) must be >= minInterval (%v)", name, minInterval, maxInterval)) + } + if timer == nil { + panic(fmt.Sprintf("%s: timer must be non-nil", name)) + } + + bfr := &BoundedFrequencyRunner{ + name: name, + fn: fn, + minInterval: minInterval, + maxInterval: maxInterval, + run: make(chan struct{}, 1), + timer: timer, + } + if minInterval == 0 { + bfr.limiter = nullLimiter{} + } else { + // allow burst updates in short succession + qps := float32(time.Second) / float32(minInterval) + bfr.limiter = flowcontrol.NewTokenBucketRateLimiterWithClock(qps, burstRuns, timer) + } + return bfr +} + +// Loop handles the periodic timer and run requests. This is expected to be +// called as a goroutine. +func (bfr *BoundedFrequencyRunner) Loop(stop <-chan struct{}) { + glog.V(3).Infof("%s Loop running", bfr.name) + bfr.timer.Reset(bfr.maxInterval) + for { + select { + case <-stop: + bfr.stop() + glog.V(3).Infof("%s Loop stopping", bfr.name) + return + case <-bfr.timer.C(): + bfr.tryRun() + case <-bfr.run: + bfr.tryRun() + } + } +} + +// Run the function as soon as possible. If this is called while Loop is not +// running, the call may be deferred indefinitely. +// If there is already a queued request to call the underlying function, it +// may be dropped - it is just guaranteed that we will try calling the +// underlying function as soon as possible starting from now. +func (bfr *BoundedFrequencyRunner) Run() { + // If it takes a lot of time to run the underlying function, noone is really + // processing elements from <run> channel. So to avoid blocking here on the + // putting element to it, we simply skip it if there is already an element + // in it. + select { + case bfr.run <- struct{}{}: + default: + } +} + +// assumes the lock is not held +func (bfr *BoundedFrequencyRunner) stop() { + bfr.mu.Lock() + defer bfr.mu.Unlock() + bfr.limiter.Stop() + bfr.timer.Stop() +} + +// assumes the lock is not held +func (bfr *BoundedFrequencyRunner) tryRun() { + bfr.mu.Lock() + defer bfr.mu.Unlock() + + if bfr.limiter.TryAccept() { + // We're allowed to run the function right now. + bfr.fn() + bfr.lastRun = bfr.timer.Now() + bfr.timer.Stop() + bfr.timer.Reset(bfr.maxInterval) + glog.V(3).Infof("%s: ran, next possible in %v, periodic in %v", bfr.name, bfr.minInterval, bfr.maxInterval) + return + } + + // It can't run right now, figure out when it can run next. + + elapsed := bfr.timer.Since(bfr.lastRun) // how long since last run + nextPossible := bfr.minInterval - elapsed // time to next possible run + nextScheduled := bfr.maxInterval - elapsed // time to next periodic run + glog.V(4).Infof("%s: %v since last run, possible in %v, scheduled in %v", bfr.name, elapsed, nextPossible, nextScheduled) + + if nextPossible < nextScheduled { + // Set the timer for ASAP, but don't drain here. Assuming Loop is running, + // it might get a delivery in the mean time, but that is OK. + bfr.timer.Stop() + bfr.timer.Reset(nextPossible) + glog.V(3).Infof("%s: throttled, scheduling run in %v", bfr.name, nextPossible) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/async/runner.go b/vendor/k8s.io/kubernetes/pkg/util/async/runner.go new file mode 100644 index 000000000..924f1d168 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/async/runner.go @@ -0,0 +1,58 @@ +/* +Copyright 2014 The Kubernetes 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 async + +import ( + "sync" +) + +// Runner is an abstraction to make it easy to start and stop groups of things that can be +// described by a single function which waits on a channel close to exit. +type Runner struct { + lock sync.Mutex + loopFuncs []func(stop chan struct{}) + stop *chan struct{} +} + +// NewRunner makes a runner for the given function(s). The function(s) should loop until +// the channel is closed. +func NewRunner(f ...func(stop chan struct{})) *Runner { + return &Runner{loopFuncs: f} +} + +// Start begins running. +func (r *Runner) Start() { + r.lock.Lock() + defer r.lock.Unlock() + if r.stop == nil { + c := make(chan struct{}) + r.stop = &c + for i := range r.loopFuncs { + go r.loopFuncs[i](*r.stop) + } + } +} + +// Stop stops running. +func (r *Runner) Stop() { + r.lock.Lock() + defer r.lock.Unlock() + if r.stop != nil { + close(*r.stop) + r.stop = nil + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/dbus/dbus.go b/vendor/k8s.io/kubernetes/pkg/util/dbus/dbus.go new file mode 100644 index 000000000..702d16e5d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/dbus/dbus.go @@ -0,0 +1,133 @@ +/* +Copyright 2015 The Kubernetes 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 dbus + +import ( + godbus "github.com/godbus/dbus" +) + +// Interface is an interface that presents a subset of the godbus/dbus API. Use this +// when you want to inject fakeable/mockable D-Bus behavior. +type Interface interface { + // SystemBus returns a connection to the system bus, connecting to it + // first if necessary + SystemBus() (Connection, error) + // SessionBus returns a connection to the session bus, connecting to it + // first if necessary + SessionBus() (Connection, error) +} + +// Connection represents a D-Bus connection +type Connection interface { + // Returns an Object representing the bus itself + BusObject() Object + + // Object creates a representation of a remote D-Bus object + Object(name, path string) Object + + // Signal registers or unregisters a channel to receive D-Bus signals + Signal(ch chan<- *godbus.Signal) +} + +// Object represents a remote D-Bus object +type Object interface { + // Call synchronously calls a D-Bus method + Call(method string, flags godbus.Flags, args ...interface{}) Call +} + +// Call represents a pending or completed D-Bus method call +type Call interface { + // Store returns a completed call's return values, or an error + Store(retvalues ...interface{}) error +} + +// Implements Interface in terms of actually talking to D-Bus +type dbusImpl struct { + systemBus *connImpl + sessionBus *connImpl +} + +// Implements Connection as a godbus.Conn +type connImpl struct { + conn *godbus.Conn +} + +// Implements Object as a godbus.Object +type objectImpl struct { + object godbus.BusObject +} + +// Implements Call as a godbus.Call +type callImpl struct { + call *godbus.Call +} + +// New returns a new Interface which will use godbus to talk to D-Bus +func New() Interface { + return &dbusImpl{} +} + +// SystemBus is part of Interface +func (db *dbusImpl) SystemBus() (Connection, error) { + if db.systemBus == nil { + bus, err := godbus.SystemBus() + if err != nil { + return nil, err + } + db.systemBus = &connImpl{bus} + } + + return db.systemBus, nil +} + +// SessionBus is part of Interface +func (db *dbusImpl) SessionBus() (Connection, error) { + if db.sessionBus == nil { + bus, err := godbus.SessionBus() + if err != nil { + return nil, err + } + db.sessionBus = &connImpl{bus} + } + + return db.sessionBus, nil +} + +// BusObject is part of the Connection interface +func (conn *connImpl) BusObject() Object { + return &objectImpl{conn.conn.BusObject()} +} + +// Object is part of the Connection interface +func (conn *connImpl) Object(name, path string) Object { + return &objectImpl{conn.conn.Object(name, godbus.ObjectPath(path))} +} + +// Signal is part of the Connection interface +func (conn *connImpl) Signal(ch chan<- *godbus.Signal) { + conn.conn.Signal(ch) +} + +// Call is part of the Object interface +func (obj *objectImpl) Call(method string, flags godbus.Flags, args ...interface{}) Call { + return &callImpl{obj.object.Call(method, flags, args...)} +} + +// Store is part of the Call interface +func (call *callImpl) Store(retvalues ...interface{}) error { + return call.call.Store(retvalues...) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/dbus/doc.go b/vendor/k8s.io/kubernetes/pkg/util/dbus/doc.go new file mode 100644 index 000000000..b07da628d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/dbus/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes 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 dbus provides an injectable interface and implementations for D-Bus communication +package dbus // import "k8s.io/kubernetes/pkg/util/dbus" diff --git a/vendor/k8s.io/kubernetes/pkg/util/dbus/fake_dbus.go b/vendor/k8s.io/kubernetes/pkg/util/dbus/fake_dbus.go new file mode 100644 index 000000000..44131272e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/dbus/fake_dbus.go @@ -0,0 +1,135 @@ +/* +Copyright 2015 The Kubernetes 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 dbus + +import ( + "fmt" + + godbus "github.com/godbus/dbus" +) + +// DBusFake is a simple fake Interface type. +type DBusFake struct { + systemBus *DBusFakeConnection + sessionBus *DBusFakeConnection +} + +// DBusFakeConnection represents a fake D-Bus connection +type DBusFakeConnection struct { + busObject *fakeObject + objects map[string]*fakeObject + signalHandlers []chan<- *godbus.Signal +} + +// DBusFakeHandler is used to handle fake D-Bus method calls +type DBusFakeHandler func(method string, args ...interface{}) ([]interface{}, error) + +type fakeObject struct { + handler DBusFakeHandler +} + +type fakeCall struct { + ret []interface{} + err error +} + +// NewFake returns a new Interface which will fake talking to D-Bus +func NewFake(systemBus *DBusFakeConnection, sessionBus *DBusFakeConnection) *DBusFake { + return &DBusFake{systemBus, sessionBus} +} + +func NewFakeConnection() *DBusFakeConnection { + return &DBusFakeConnection{ + objects: make(map[string]*fakeObject), + } +} + +// SystemBus is part of Interface +func (db *DBusFake) SystemBus() (Connection, error) { + if db.systemBus != nil { + return db.systemBus, nil + } else { + return nil, fmt.Errorf("DBus is not running") + } +} + +// SessionBus is part of Interface +func (db *DBusFake) SessionBus() (Connection, error) { + if db.sessionBus != nil { + return db.sessionBus, nil + } else { + return nil, fmt.Errorf("DBus is not running") + } +} + +// BusObject is part of the Connection interface +func (conn *DBusFakeConnection) BusObject() Object { + return conn.busObject +} + +// Object is part of the Connection interface +func (conn *DBusFakeConnection) Object(name, path string) Object { + return conn.objects[name+path] +} + +// Signal is part of the Connection interface +func (conn *DBusFakeConnection) Signal(ch chan<- *godbus.Signal) { + for i := range conn.signalHandlers { + if conn.signalHandlers[i] == ch { + conn.signalHandlers = append(conn.signalHandlers[:i], conn.signalHandlers[i+1:]...) + return + } + } + conn.signalHandlers = append(conn.signalHandlers, ch) +} + +// SetBusObject sets the handler for the BusObject of conn +func (conn *DBusFakeConnection) SetBusObject(handler DBusFakeHandler) { + conn.busObject = &fakeObject{handler} +} + +// AddObject adds a handler for the Object at name and path +func (conn *DBusFakeConnection) AddObject(name, path string, handler DBusFakeHandler) { + conn.objects[name+path] = &fakeObject{handler} +} + +// EmitSignal emits a signal on conn +func (conn *DBusFakeConnection) EmitSignal(name, path, iface, signal string, args ...interface{}) { + sig := &godbus.Signal{ + Sender: name, + Path: godbus.ObjectPath(path), + Name: iface + "." + signal, + Body: args, + } + for _, ch := range conn.signalHandlers { + ch <- sig + } +} + +// Call is part of the Object interface +func (obj *fakeObject) Call(method string, flags godbus.Flags, args ...interface{}) Call { + ret, err := obj.handler(method, args...) + return &fakeCall{ret, err} +} + +// Store is part of the Call interface +func (call *fakeCall) Store(retvalues ...interface{}) error { + if call.err != nil { + return call.err + } + return godbus.Store(call.ret, retvalues...) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/doc.go b/vendor/k8s.io/kubernetes/pkg/util/doc.go new file mode 100644 index 000000000..f7e214f31 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2014 The Kubernetes 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 util implements various utility functions used in both testing and implementation +// of Kubernetes. Package util may not depend on any other package in the Kubernetes +// package tree. +package util // import "k8s.io/kubernetes/pkg/util" diff --git a/vendor/k8s.io/kubernetes/pkg/util/exec/doc.go b/vendor/k8s.io/kubernetes/pkg/util/exec/doc.go new file mode 100644 index 000000000..de7301c8d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/exec/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes 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 exec provides an injectable interface and implementations for running commands. +package exec // import "k8s.io/kubernetes/pkg/util/exec" diff --git a/vendor/k8s.io/kubernetes/pkg/util/exec/exec.go b/vendor/k8s.io/kubernetes/pkg/util/exec/exec.go new file mode 100644 index 000000000..f43bfa7a1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/exec/exec.go @@ -0,0 +1,200 @@ +/* +Copyright 2014 The Kubernetes 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 exec + +import ( + "io" + osexec "os/exec" + "syscall" + "time" +) + +// ErrExecutableNotFound is returned if the executable is not found. +var ErrExecutableNotFound = osexec.ErrNotFound + +// Interface is an interface that presents a subset of the os/exec API. Use this +// when you want to inject fakeable/mockable exec behavior. +type Interface interface { + // Command returns a Cmd instance which can be used to run a single command. + // This follows the pattern of package os/exec. + Command(cmd string, args ...string) Cmd + + // LookPath wraps os/exec.LookPath + LookPath(file string) (string, error) +} + +// Cmd is an interface that presents an API that is very similar to Cmd from os/exec. +// As more functionality is needed, this can grow. Since Cmd is a struct, we will have +// to replace fields with get/set method pairs. +type Cmd interface { + // Run runs the command to the completion. + Run() error + // CombinedOutput runs the command and returns its combined standard output + // and standard error. This follows the pattern of package os/exec. + CombinedOutput() ([]byte, error) + // Output runs the command and returns standard output, but not standard err + Output() ([]byte, error) + SetDir(dir string) + SetStdin(in io.Reader) + SetStdout(out io.Writer) + SetStderr(out io.Writer) + // Stops the command by sending SIGTERM. It is not guaranteed the + // process will stop before this function returns. If the process is not + // responding, an internal timer function will send a SIGKILL to force + // terminate after 10 seconds. + Stop() +} + +// ExitError is an interface that presents an API similar to os.ProcessState, which is +// what ExitError from os/exec is. This is designed to make testing a bit easier and +// probably loses some of the cross-platform properties of the underlying library. +type ExitError interface { + String() string + Error() string + Exited() bool + ExitStatus() int +} + +// Implements Interface in terms of really exec()ing. +type executor struct{} + +// New returns a new Interface which will os/exec to run commands. +func New() Interface { + return &executor{} +} + +// Command is part of the Interface interface. +func (executor *executor) Command(cmd string, args ...string) Cmd { + return (*cmdWrapper)(osexec.Command(cmd, args...)) +} + +// LookPath is part of the Interface interface +func (executor *executor) LookPath(file string) (string, error) { + return osexec.LookPath(file) +} + +// Wraps exec.Cmd so we can capture errors. +type cmdWrapper osexec.Cmd + +func (cmd *cmdWrapper) SetDir(dir string) { + cmd.Dir = dir +} + +func (cmd *cmdWrapper) SetStdin(in io.Reader) { + cmd.Stdin = in +} + +func (cmd *cmdWrapper) SetStdout(out io.Writer) { + cmd.Stdout = out +} + +func (cmd *cmdWrapper) SetStderr(out io.Writer) { + cmd.Stderr = out +} + +// Run is part of the Cmd interface. +func (cmd *cmdWrapper) Run() error { + return (*osexec.Cmd)(cmd).Run() +} + +// CombinedOutput is part of the Cmd interface. +func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) { + out, err := (*osexec.Cmd)(cmd).CombinedOutput() + if err != nil { + return out, handleError(err) + } + return out, nil +} + +func (cmd *cmdWrapper) Output() ([]byte, error) { + out, err := (*osexec.Cmd)(cmd).Output() + if err != nil { + return out, handleError(err) + } + return out, nil +} + +// Stop is part of the Cmd interface. +func (cmd *cmdWrapper) Stop() { + c := (*osexec.Cmd)(cmd) + if c.ProcessState.Exited() { + return + } + c.Process.Signal(syscall.SIGTERM) + time.AfterFunc(10*time.Second, func() { + if c.ProcessState.Exited() { + return + } + c.Process.Signal(syscall.SIGKILL) + }) +} + +func handleError(err error) error { + if ee, ok := err.(*osexec.ExitError); ok { + // Force a compile fail if exitErrorWrapper can't convert to ExitError. + var x ExitError = &ExitErrorWrapper{ee} + return x + } + if ee, ok := err.(*osexec.Error); ok { + if ee.Err == osexec.ErrNotFound { + return ErrExecutableNotFound + } + } + return err +} + +// ExitErrorWrapper is an implementation of ExitError in terms of os/exec ExitError. +// Note: standard exec.ExitError is type *os.ProcessState, which already implements Exited(). +type ExitErrorWrapper struct { + *osexec.ExitError +} + +var _ ExitError = ExitErrorWrapper{} + +// ExitStatus is part of the ExitError interface. +func (eew ExitErrorWrapper) ExitStatus() int { + ws, ok := eew.Sys().(syscall.WaitStatus) + if !ok { + panic("can't call ExitStatus() on a non-WaitStatus exitErrorWrapper") + } + return ws.ExitStatus() +} + +// CodeExitError is an implementation of ExitError consisting of an error object +// and an exit code (the upper bits of os.exec.ExitStatus). +type CodeExitError struct { + Err error + Code int +} + +var _ ExitError = CodeExitError{} + +func (e CodeExitError) Error() string { + return e.Err.Error() +} + +func (e CodeExitError) String() string { + return e.Err.Error() +} + +func (e CodeExitError) Exited() bool { + return true +} + +func (e CodeExitError) ExitStatus() int { + return e.Code +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/exec/fake_exec.go b/vendor/k8s.io/kubernetes/pkg/util/exec/fake_exec.go new file mode 100644 index 000000000..c7fcd6cec --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/exec/fake_exec.go @@ -0,0 +1,145 @@ +/* +Copyright 2014 The Kubernetes 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 exec + +import ( + "fmt" + "io" +) + +// A simple scripted Interface type. +type FakeExec struct { + CommandScript []FakeCommandAction + CommandCalls int + LookPathFunc func(string) (string, error) +} + +type FakeCommandAction func(cmd string, args ...string) Cmd + +func (fake *FakeExec) Command(cmd string, args ...string) Cmd { + if fake.CommandCalls > len(fake.CommandScript)-1 { + panic(fmt.Sprintf("ran out of Command() actions. Could not handle command [%d]: %s args: %v", fake.CommandCalls, cmd, args)) + } + i := fake.CommandCalls + fake.CommandCalls++ + return fake.CommandScript[i](cmd, args...) +} + +func (fake *FakeExec) LookPath(file string) (string, error) { + return fake.LookPathFunc(file) +} + +// A simple scripted Cmd type. +type FakeCmd struct { + Argv []string + CombinedOutputScript []FakeCombinedOutputAction + CombinedOutputCalls int + CombinedOutputLog [][]string + RunScript []FakeRunAction + RunCalls int + RunLog [][]string + Dirs []string + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +func InitFakeCmd(fake *FakeCmd, cmd string, args ...string) Cmd { + fake.Argv = append([]string{cmd}, args...) + return fake +} + +type FakeCombinedOutputAction func() ([]byte, error) +type FakeRunAction func() ([]byte, []byte, error) + +func (fake *FakeCmd) SetDir(dir string) { + fake.Dirs = append(fake.Dirs, dir) +} + +func (fake *FakeCmd) SetStdin(in io.Reader) { + fake.Stdin = in +} + +func (fake *FakeCmd) SetStdout(out io.Writer) { + fake.Stdout = out +} + +func (fake *FakeCmd) SetStderr(out io.Writer) { + fake.Stderr = out +} + +func (fake *FakeCmd) Run() error { + if fake.RunCalls > len(fake.RunScript)-1 { + panic("ran out of Run() actions") + } + if fake.RunLog == nil { + fake.RunLog = [][]string{} + } + i := fake.RunCalls + fake.RunLog = append(fake.RunLog, append([]string{}, fake.Argv...)) + fake.RunCalls++ + stdout, stderr, err := fake.RunScript[i]() + if stdout != nil { + fake.Stdout.Write(stdout) + } + if stderr != nil { + fake.Stderr.Write(stderr) + } + return err +} + +func (fake *FakeCmd) CombinedOutput() ([]byte, error) { + if fake.CombinedOutputCalls > len(fake.CombinedOutputScript)-1 { + panic("ran out of CombinedOutput() actions") + } + if fake.CombinedOutputLog == nil { + fake.CombinedOutputLog = [][]string{} + } + i := fake.CombinedOutputCalls + fake.CombinedOutputLog = append(fake.CombinedOutputLog, append([]string{}, fake.Argv...)) + fake.CombinedOutputCalls++ + return fake.CombinedOutputScript[i]() +} + +func (fake *FakeCmd) Output() ([]byte, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (fake *FakeCmd) Stop() { + // no-op +} + +// A simple fake ExitError type. +type FakeExitError struct { + Status int +} + +func (fake *FakeExitError) String() string { + return fmt.Sprintf("exit %d", fake.Status) +} + +func (fake *FakeExitError) Error() string { + return fake.String() +} + +func (fake *FakeExitError) Exited() bool { + return true +} + +func (fake *FakeExitError) ExitStatus() int { + return fake.Status +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/hash/hash.go b/vendor/k8s.io/kubernetes/pkg/util/hash/hash.go new file mode 100644 index 000000000..803f066a4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/hash/hash.go @@ -0,0 +1,37 @@ +/* +Copyright 2015 The Kubernetes 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 hash + +import ( + "hash" + + "github.com/davecgh/go-spew/spew" +) + +// DeepHashObject writes specified object to hash using the spew library +// which follows pointers and prints actual values of the nested objects +// ensuring the hash does not change when a pointer changes. +func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) { + hasher.Reset() + printer := spew.ConfigState{ + Indent: " ", + SortKeys: true, + DisableMethods: true, + SpewKeys: true, + } + printer.Fprintf(hasher, "%#v", objectToWrite) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/interrupt/interrupt.go b/vendor/k8s.io/kubernetes/pkg/util/interrupt/interrupt.go new file mode 100644 index 000000000..0265b9fb1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/interrupt/interrupt.go @@ -0,0 +1,104 @@ +/* +Copyright 2016 The Kubernetes 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 interrupt + +import ( + "os" + "os/signal" + "sync" + "syscall" +) + +// terminationSignals are signals that cause the program to exit in the +// supported platforms (linux, darwin, windows). +var terminationSignals = []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT} + +// Handler guarantees execution of notifications after a critical section (the function passed +// to a Run method), even in the presence of process termination. It guarantees exactly once +// invocation of the provided notify functions. +type Handler struct { + notify []func() + final func(os.Signal) + once sync.Once +} + +// Chain creates a new handler that invokes all notify functions when the critical section exits +// and then invokes the optional handler's notifications. This allows critical sections to be +// nested without losing exactly once invocations. Notify functions can invoke any cleanup needed +// but should not exit (which is the responsibility of the parent handler). +func Chain(handler *Handler, notify ...func()) *Handler { + if handler == nil { + return New(nil, notify...) + } + return New(handler.Signal, append(notify, handler.Close)...) +} + +// New creates a new handler that guarantees all notify functions are run after the critical +// section exits (or is interrupted by the OS), then invokes the final handler. If no final +// handler is specified, the default final is `os.Exit(1)`. A handler can only be used for +// one critical section. +func New(final func(os.Signal), notify ...func()) *Handler { + return &Handler{ + final: final, + notify: notify, + } +} + +// Close executes all the notification handlers if they have not yet been executed. +func (h *Handler) Close() { + h.once.Do(func() { + for _, fn := range h.notify { + fn() + } + }) +} + +// Signal is called when an os.Signal is received, and guarantees that all notifications +// are executed, then the final handler is executed. This function should only be called once +// per Handler instance. +func (h *Handler) Signal(s os.Signal) { + h.once.Do(func() { + for _, fn := range h.notify { + fn() + } + if h.final == nil { + os.Exit(1) + } + h.final(s) + }) +} + +// Run ensures that any notifications are invoked after the provided fn exits (even if the +// process is interrupted by an OS termination signal). Notifications are only invoked once +// per Handler instance, so calling Run more than once will not behave as the user expects. +func (h *Handler) Run(fn func() error) error { + ch := make(chan os.Signal, 1) + signal.Notify(ch, terminationSignals...) + defer func() { + signal.Stop(ch) + close(ch) + }() + go func() { + sig, ok := <-ch + if !ok { + return + } + h.Signal(sig) + }() + defer h.Close() + return fn() +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/io/io.go b/vendor/k8s.io/kubernetes/pkg/util/io/io.go new file mode 100644 index 000000000..0be8e1272 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/io/io.go @@ -0,0 +1,61 @@ +/* +Copyright 2015 The Kubernetes 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 io + +import ( + "fmt" + "io/ioutil" + "os" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" +) + +// LoadPodFromFile will read, decode, and return a Pod from a file. +func LoadPodFromFile(filePath string) (*v1.Pod, error) { + if filePath == "" { + return nil, fmt.Errorf("file path not specified") + } + podDef, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("failed to read file path %s: %+v", filePath, err) + } + if len(podDef) == 0 { + return nil, fmt.Errorf("file was empty: %s", filePath) + } + pod := &v1.Pod{} + + codec := api.Codecs.LegacyCodec(api.Registry.GroupOrDie(v1.GroupName).GroupVersion) + if err := runtime.DecodeInto(codec, podDef, pod); err != nil { + return nil, fmt.Errorf("failed decoding file: %v", err) + } + return pod, nil +} + +// SavePodToFile will encode and save a pod to a given path & permissions +func SavePodToFile(pod *v1.Pod, filePath string, perm os.FileMode) error { + if filePath == "" { + return fmt.Errorf("file path not specified") + } + codec := api.Codecs.LegacyCodec(api.Registry.GroupOrDie(v1.GroupName).GroupVersion) + data, err := runtime.Encode(codec, pod) + if err != nil { + return fmt.Errorf("failed encoding pod: %v", err) + } + return ioutil.WriteFile(filePath, data, perm) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/io/writer.go b/vendor/k8s.io/kubernetes/pkg/util/io/writer.go new file mode 100644 index 000000000..086508c90 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/io/writer.go @@ -0,0 +1,80 @@ +/* +Copyright 2015 The Kubernetes 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 io + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + + "github.com/golang/glog" +) + +// Writer is an interface which allows to write data to a file. +type Writer interface { + WriteFile(filename string, data []byte, perm os.FileMode) error +} + +// StdWriter implements Writer interface and uses standard libraries +// for writing data to files. +type StdWriter struct { +} + +func (writer *StdWriter) WriteFile(filename string, data []byte, perm os.FileMode) error { + return ioutil.WriteFile(filename, data, perm) +} + +// Alternative implementation of Writer interface that allows writing data to file +// using nsenter command. +// If a program (e.g. kubelet) runs in a container it may want to write data to +// a mounted device. Since in Docker, mount propagation mode is set to private, +// it will not see the mounted device in its own namespace. To work around this +// limitaion one has to first enter hosts namespace (by using 'nsenter') and only +// then write data. +type NsenterWriter struct { +} + +// TODO: should take a writer, not []byte +func (writer *NsenterWriter) WriteFile(filename string, data []byte, perm os.FileMode) error { + cmd := "nsenter" + base_args := []string{ + "--mount=/rootfs/proc/1/ns/mnt", + "--", + } + + echo_args := append(base_args, "sh", "-c", fmt.Sprintf("cat > %s", filename)) + glog.V(5).Infof("Command to write data to file: %v %v", cmd, echo_args) + command := exec.Command(cmd, echo_args...) + command.Stdin = bytes.NewBuffer(data) + outputBytes, err := command.CombinedOutput() + if err != nil { + glog.Errorf("Output from writing to %q: %v", filename, string(outputBytes)) + return err + } + + chmod_args := append(base_args, "chmod", fmt.Sprintf("%o", perm), filename) + glog.V(5).Infof("Command to change permissions to file: %v %v", cmd, chmod_args) + outputBytes, err = exec.Command(cmd, chmod_args...).CombinedOutput() + if err != nil { + glog.Errorf("Output from chmod command: %v", string(outputBytes)) + return err + } + + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/iptables/doc.go b/vendor/k8s.io/kubernetes/pkg/util/iptables/doc.go new file mode 100644 index 000000000..f26498293 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/iptables/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes 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 iptables provides an interface and implementations for running iptables commands. +package iptables // import "k8s.io/kubernetes/pkg/util/iptables" diff --git a/vendor/k8s.io/kubernetes/pkg/util/iptables/iptables.go b/vendor/k8s.io/kubernetes/pkg/util/iptables/iptables.go new file mode 100644 index 000000000..b6c08fa37 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/iptables/iptables.go @@ -0,0 +1,652 @@ +/* +Copyright 2014 The Kubernetes 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 iptables + +import ( + "bytes" + "fmt" + "regexp" + "strings" + "sync" + + godbus "github.com/godbus/dbus" + "github.com/golang/glog" + "k8s.io/apimachinery/pkg/util/sets" + utildbus "k8s.io/kubernetes/pkg/util/dbus" + utilexec "k8s.io/kubernetes/pkg/util/exec" + utilversion "k8s.io/kubernetes/pkg/util/version" +) + +type RulePosition string + +const ( + Prepend RulePosition = "-I" + Append RulePosition = "-A" +) + +// An injectable interface for running iptables commands. Implementations must be goroutine-safe. +type Interface interface { + // GetVersion returns the "X.Y.Z" version string for iptables. + GetVersion() (string, error) + // EnsureChain checks if the specified chain exists and, if not, creates it. If the chain existed, return true. + EnsureChain(table Table, chain Chain) (bool, error) + // FlushChain clears the specified chain. If the chain did not exist, return error. + FlushChain(table Table, chain Chain) error + // DeleteChain deletes the specified chain. If the chain did not exist, return error. + DeleteChain(table Table, chain Chain) error + // EnsureRule checks if the specified rule is present and, if not, creates it. If the rule existed, return true. + EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) + // DeleteRule checks if the specified rule is present and, if so, deletes it. + DeleteRule(table Table, chain Chain, args ...string) error + // IsIpv6 returns true if this is managing ipv6 tables + IsIpv6() bool + // SaveInto calls `iptables-save` for table and stores result in a given buffer. + SaveInto(table Table, buffer *bytes.Buffer) error + // Restore runs `iptables-restore` passing data through []byte. + // table is the Table to restore + // data should be formatted like the output of SaveInto() + // flush sets the presence of the "--noflush" flag. see: FlushFlag + // counters sets the "--counters" flag. see: RestoreCountersFlag + Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error + // RestoreAll is the same as Restore except that no table is specified. + RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error + // AddReloadFunc adds a function to call on iptables reload + AddReloadFunc(reloadFunc func()) + // Destroy cleans up resources used by the Interface + Destroy() +} + +type Protocol byte + +const ( + ProtocolIpv4 Protocol = iota + 1 + ProtocolIpv6 +) + +type Table string + +const ( + TableNAT Table = "nat" + TableFilter Table = "filter" +) + +type Chain string + +const ( + ChainPostrouting Chain = "POSTROUTING" + ChainPrerouting Chain = "PREROUTING" + ChainOutput Chain = "OUTPUT" + ChainInput Chain = "INPUT" +) + +const ( + cmdIPTablesSave string = "iptables-save" + cmdIPTablesRestore string = "iptables-restore" + cmdIPTables string = "iptables" + cmdIp6tables string = "ip6tables" +) + +// Option flag for Restore +type RestoreCountersFlag bool + +const RestoreCounters RestoreCountersFlag = true +const NoRestoreCounters RestoreCountersFlag = false + +// Option flag for Flush +type FlushFlag bool + +const FlushTables FlushFlag = true +const NoFlushTables FlushFlag = false + +// Versions of iptables less than this do not support the -C / --check flag +// (test whether a rule exists). +const MinCheckVersion = "1.4.11" + +// Minimum iptables versions supporting the -w and -w2 flags +const MinWaitVersion = "1.4.20" +const MinWait2Version = "1.4.22" + +const LockfilePath16x = "/run/xtables.lock" + +// runner implements Interface in terms of exec("iptables"). +type runner struct { + mu sync.Mutex + exec utilexec.Interface + dbus utildbus.Interface + protocol Protocol + hasCheck bool + waitFlag []string + restoreWaitFlag []string + lockfilePath string + + reloadFuncs []func() + signal chan *godbus.Signal +} + +// newInternal returns a new Interface which will exec iptables, and allows the +// caller to change the iptables-restore lockfile path +func newInternal(exec utilexec.Interface, dbus utildbus.Interface, protocol Protocol, lockfilePath string) Interface { + vstring, err := getIPTablesVersionString(exec) + if err != nil { + glog.Warningf("Error checking iptables version, assuming version at least %s: %v", MinCheckVersion, err) + vstring = MinCheckVersion + } + + if lockfilePath == "" { + lockfilePath = LockfilePath16x + } + + runner := &runner{ + exec: exec, + dbus: dbus, + protocol: protocol, + hasCheck: getIPTablesHasCheckCommand(vstring), + waitFlag: getIPTablesWaitFlag(vstring), + restoreWaitFlag: getIPTablesRestoreWaitFlag(exec), + lockfilePath: lockfilePath, + } + // TODO this needs to be moved to a separate Start() or Run() function so that New() has zero side + // effects. + runner.connectToFirewallD() + return runner +} + +// New returns a new Interface which will exec iptables. +func New(exec utilexec.Interface, dbus utildbus.Interface, protocol Protocol) Interface { + return newInternal(exec, dbus, protocol, "") +} + +// Destroy is part of Interface. +func (runner *runner) Destroy() { + if runner.signal != nil { + runner.signal <- nil + } +} + +const ( + firewalldName = "org.fedoraproject.FirewallD1" + firewalldPath = "/org/fedoraproject/FirewallD1" + firewalldInterface = "org.fedoraproject.FirewallD1" +) + +// Connects to D-Bus and listens for FirewallD start/restart. (On non-FirewallD-using +// systems, this is effectively a no-op; we listen for the signals, but they will never be +// emitted, so reload() will never be called.) +func (runner *runner) connectToFirewallD() { + bus, err := runner.dbus.SystemBus() + if err != nil { + glog.V(1).Infof("Could not connect to D-Bus system bus: %s", err) + return + } + + rule := fmt.Sprintf("type='signal',sender='%s',path='%s',interface='%s',member='Reloaded'", firewalldName, firewalldPath, firewalldInterface) + bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) + + rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", firewalldName) + bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) + + runner.signal = make(chan *godbus.Signal, 10) + bus.Signal(runner.signal) + + go runner.dbusSignalHandler(bus) +} + +// GetVersion returns the version string. +func (runner *runner) GetVersion() (string, error) { + return getIPTablesVersionString(runner.exec) +} + +// EnsureChain is part of Interface. +func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) { + fullArgs := makeFullArgs(table, chain) + + runner.mu.Lock() + defer runner.mu.Unlock() + + out, err := runner.run(opCreateChain, fullArgs) + if err != nil { + if ee, ok := err.(utilexec.ExitError); ok { + if ee.Exited() && ee.ExitStatus() == 1 { + return true, nil + } + } + return false, fmt.Errorf("error creating chain %q: %v: %s", chain, err, out) + } + return false, nil +} + +// FlushChain is part of Interface. +func (runner *runner) FlushChain(table Table, chain Chain) error { + fullArgs := makeFullArgs(table, chain) + + runner.mu.Lock() + defer runner.mu.Unlock() + + out, err := runner.run(opFlushChain, fullArgs) + if err != nil { + return fmt.Errorf("error flushing chain %q: %v: %s", chain, err, out) + } + return nil +} + +// DeleteChain is part of Interface. +func (runner *runner) DeleteChain(table Table, chain Chain) error { + fullArgs := makeFullArgs(table, chain) + + runner.mu.Lock() + defer runner.mu.Unlock() + + // TODO: we could call iptables -S first, ignore the output and check for non-zero return (more like DeleteRule) + out, err := runner.run(opDeleteChain, fullArgs) + if err != nil { + return fmt.Errorf("error deleting chain %q: %v: %s", chain, err, out) + } + return nil +} + +// EnsureRule is part of Interface. +func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) { + fullArgs := makeFullArgs(table, chain, args...) + + runner.mu.Lock() + defer runner.mu.Unlock() + + exists, err := runner.checkRule(table, chain, args...) + if err != nil { + return false, err + } + if exists { + return true, nil + } + out, err := runner.run(operation(position), fullArgs) + if err != nil { + return false, fmt.Errorf("error appending rule: %v: %s", err, out) + } + return false, nil +} + +// DeleteRule is part of Interface. +func (runner *runner) DeleteRule(table Table, chain Chain, args ...string) error { + fullArgs := makeFullArgs(table, chain, args...) + + runner.mu.Lock() + defer runner.mu.Unlock() + + exists, err := runner.checkRule(table, chain, args...) + if err != nil { + return err + } + if !exists { + return nil + } + out, err := runner.run(opDeleteRule, fullArgs) + if err != nil { + return fmt.Errorf("error deleting rule: %v: %s", err, out) + } + return nil +} + +func (runner *runner) IsIpv6() bool { + return runner.protocol == ProtocolIpv6 +} + +// SaveInto is part of Interface. +func (runner *runner) SaveInto(table Table, buffer *bytes.Buffer) error { + runner.mu.Lock() + defer runner.mu.Unlock() + + // run and return + args := []string{"-t", string(table)} + glog.V(4).Infof("running iptables-save %v", args) + cmd := runner.exec.Command(cmdIPTablesSave, args...) + // Since CombinedOutput() doesn't support redirecting it to a buffer, + // we need to workaround it by redirecting stdout and stderr to buffer + // and explicitly calling Run() [CombinedOutput() underneath itself + // creates a new buffer, redirects stdout and stderr to it and also + // calls Run()]. + cmd.SetStdout(buffer) + cmd.SetStderr(buffer) + return cmd.Run() +} + +// Restore is part of Interface. +func (runner *runner) Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error { + // setup args + args := []string{"-T", string(table)} + return runner.restoreInternal(args, data, flush, counters) +} + +// RestoreAll is part of Interface. +func (runner *runner) RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error { + // setup args + args := make([]string, 0) + return runner.restoreInternal(args, data, flush, counters) +} + +type iptablesLocker interface { + Close() +} + +// restoreInternal is the shared part of Restore/RestoreAll +func (runner *runner) restoreInternal(args []string, data []byte, flush FlushFlag, counters RestoreCountersFlag) error { + runner.mu.Lock() + defer runner.mu.Unlock() + + if !flush { + args = append(args, "--noflush") + } + if counters { + args = append(args, "--counters") + } + + // Grab the iptables lock to prevent iptables-restore and iptables + // from stepping on each other. iptables-restore 1.6.2 will have + // a --wait option like iptables itself, but that's not widely deployed. + if len(runner.restoreWaitFlag) == 0 { + locker, err := grabIptablesLocks(runner.lockfilePath) + if err != nil { + return err + } + defer locker.Close() + } + + // run the command and return the output or an error including the output and error + fullArgs := append(runner.restoreWaitFlag, args...) + glog.V(4).Infof("running iptables-restore %v", fullArgs) + cmd := runner.exec.Command(cmdIPTablesRestore, fullArgs...) + cmd.SetStdin(bytes.NewBuffer(data)) + b, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%v (%s)", err, b) + } + return nil +} + +func (runner *runner) iptablesCommand() string { + if runner.IsIpv6() { + return cmdIp6tables + } else { + return cmdIPTables + } +} + +func (runner *runner) run(op operation, args []string) ([]byte, error) { + iptablesCmd := runner.iptablesCommand() + + fullArgs := append(runner.waitFlag, string(op)) + fullArgs = append(fullArgs, args...) + glog.V(5).Infof("running iptables %s %v", string(op), args) + return runner.exec.Command(iptablesCmd, fullArgs...).CombinedOutput() + // Don't log err here - callers might not think it is an error. +} + +// Returns (bool, nil) if it was able to check the existence of the rule, or +// (<undefined>, error) if the process of checking failed. +func (runner *runner) checkRule(table Table, chain Chain, args ...string) (bool, error) { + if runner.hasCheck { + return runner.checkRuleUsingCheck(makeFullArgs(table, chain, args...)) + } else { + return runner.checkRuleWithoutCheck(table, chain, args...) + } +} + +var hexnumRE = regexp.MustCompile("0x0+([0-9])") + +func trimhex(s string) string { + return hexnumRE.ReplaceAllString(s, "0x$1") +} + +// Executes the rule check without using the "-C" flag, instead parsing iptables-save. +// Present for compatibility with <1.4.11 versions of iptables. This is full +// of hack and half-measures. We should nix this ASAP. +func (runner *runner) checkRuleWithoutCheck(table Table, chain Chain, args ...string) (bool, error) { + glog.V(1).Infof("running iptables-save -t %s", string(table)) + out, err := runner.exec.Command(cmdIPTablesSave, "-t", string(table)).CombinedOutput() + if err != nil { + return false, fmt.Errorf("error checking rule: %v", err) + } + + // Sadly, iptables has inconsistent quoting rules for comments. Just remove all quotes. + // Also, quoted multi-word comments (which are counted as a single arg) + // will be unpacked into multiple args, + // in order to compare against iptables-save output (which will be split at whitespace boundary) + // e.g. a single arg('"this must be before the NodePort rules"') will be unquoted and unpacked into 7 args. + var argsCopy []string + for i := range args { + tmpField := strings.Trim(args[i], "\"") + tmpField = trimhex(tmpField) + argsCopy = append(argsCopy, strings.Fields(tmpField)...) + } + argset := sets.NewString(argsCopy...) + + for _, line := range strings.Split(string(out), "\n") { + var fields = strings.Fields(line) + + // Check that this is a rule for the correct chain, and that it has + // the correct number of argument (+2 for "-A <chain name>") + if !strings.HasPrefix(line, fmt.Sprintf("-A %s", string(chain))) || len(fields) != len(argsCopy)+2 { + continue + } + + // Sadly, iptables has inconsistent quoting rules for comments. + // Just remove all quotes. + for i := range fields { + fields[i] = strings.Trim(fields[i], "\"") + fields[i] = trimhex(fields[i]) + } + + // TODO: This misses reorderings e.g. "-x foo ! -y bar" will match "! -x foo -y bar" + if sets.NewString(fields...).IsSuperset(argset) { + return true, nil + } + glog.V(5).Infof("DBG: fields is not a superset of args: fields=%v args=%v", fields, args) + } + + return false, nil +} + +// Executes the rule check using the "-C" flag +func (runner *runner) checkRuleUsingCheck(args []string) (bool, error) { + out, err := runner.run(opCheckRule, args) + if err == nil { + return true, nil + } + if ee, ok := err.(utilexec.ExitError); ok { + // iptables uses exit(1) to indicate a failure of the operation, + // as compared to a malformed commandline, for example. + if ee.Exited() && ee.ExitStatus() == 1 { + return false, nil + } + } + return false, fmt.Errorf("error checking rule: %v: %s", err, out) +} + +type operation string + +const ( + opCreateChain operation = "-N" + opFlushChain operation = "-F" + opDeleteChain operation = "-X" + opAppendRule operation = "-A" + opCheckRule operation = "-C" + opDeleteRule operation = "-D" +) + +func makeFullArgs(table Table, chain Chain, args ...string) []string { + return append([]string{string(chain), "-t", string(table)}, args...) +} + +// Checks if iptables has the "-C" flag +func getIPTablesHasCheckCommand(vstring string) bool { + minVersion, err := utilversion.ParseGeneric(MinCheckVersion) + if err != nil { + glog.Errorf("MinCheckVersion (%s) is not a valid version string: %v", MinCheckVersion, err) + return true + } + version, err := utilversion.ParseGeneric(vstring) + if err != nil { + glog.Errorf("vstring (%s) is not a valid version string: %v", vstring, err) + return true + } + return version.AtLeast(minVersion) +} + +// Checks if iptables version has a "wait" flag +func getIPTablesWaitFlag(vstring string) []string { + version, err := utilversion.ParseGeneric(vstring) + if err != nil { + glog.Errorf("vstring (%s) is not a valid version string: %v", vstring, err) + return nil + } + + minVersion, err := utilversion.ParseGeneric(MinWaitVersion) + if err != nil { + glog.Errorf("MinWaitVersion (%s) is not a valid version string: %v", MinWaitVersion, err) + return nil + } + if version.LessThan(minVersion) { + return nil + } + + minVersion, err = utilversion.ParseGeneric(MinWait2Version) + if err != nil { + glog.Errorf("MinWait2Version (%s) is not a valid version string: %v", MinWait2Version, err) + return nil + } + if version.LessThan(minVersion) { + return []string{"-w"} + } else { + return []string{"-w2"} + } +} + +// getIPTablesVersionString runs "iptables --version" to get the version string +// in the form "X.X.X" +func getIPTablesVersionString(exec utilexec.Interface) (string, error) { + // this doesn't access mutable state so we don't need to use the interface / runner + bytes, err := exec.Command(cmdIPTables, "--version").CombinedOutput() + if err != nil { + return "", err + } + versionMatcher := regexp.MustCompile("v([0-9]+(\\.[0-9]+)+)") + match := versionMatcher.FindStringSubmatch(string(bytes)) + if match == nil { + return "", fmt.Errorf("no iptables version found in string: %s", bytes) + } + return match[1], nil +} + +// Checks if iptables-restore has a "wait" flag +// --wait support landed in v1.6.1+ right before --version support, so +// any version of iptables-restore that supports --version will also +// support --wait +func getIPTablesRestoreWaitFlag(exec utilexec.Interface) []string { + vstring, err := getIPTablesRestoreVersionString(exec) + if err != nil || vstring == "" { + glog.V(3).Infof("couldn't get iptables-restore version; assuming it doesn't support --wait") + return nil + } + if _, err := utilversion.ParseGeneric(vstring); err != nil { + glog.V(3).Infof("couldn't parse iptables-restore version; assuming it doesn't support --wait") + return nil + } + + return []string{"--wait=2"} +} + +// getIPTablesRestoreVersionString runs "iptables-restore --version" to get the version string +// in the form "X.X.X" +func getIPTablesRestoreVersionString(exec utilexec.Interface) (string, error) { + // this doesn't access mutable state so we don't need to use the interface / runner + + // iptables-restore hasn't always had --version, and worse complains + // about unrecognized commands but doesn't exit when it gets them. + // Work around that by setting stdin to nothing so it exits immediately. + cmd := exec.Command(cmdIPTablesRestore, "--version") + cmd.SetStdin(bytes.NewReader([]byte{})) + bytes, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + versionMatcher := regexp.MustCompile("v([0-9]+(\\.[0-9]+)+)") + match := versionMatcher.FindStringSubmatch(string(bytes)) + if match == nil { + return "", fmt.Errorf("no iptables version found in string: %s", bytes) + } + return match[1], nil +} + +// goroutine to listen for D-Bus signals +func (runner *runner) dbusSignalHandler(bus utildbus.Connection) { + firewalld := bus.Object(firewalldName, firewalldPath) + + for s := range runner.signal { + if s == nil { + // Unregister + bus.Signal(runner.signal) + return + } + + switch s.Name { + case "org.freedesktop.DBus.NameOwnerChanged": + name := s.Body[0].(string) + new_owner := s.Body[2].(string) + + if name != firewalldName || len(new_owner) == 0 { + continue + } + + // FirewallD startup (specifically the part where it deletes + // all existing iptables rules) may not yet be complete when + // we get this signal, so make a dummy request to it to + // synchronize. + firewalld.Call(firewalldInterface+".getDefaultZone", 0) + + runner.reload() + case firewalldInterface + ".Reloaded": + runner.reload() + } + } +} + +// AddReloadFunc is part of Interface +func (runner *runner) AddReloadFunc(reloadFunc func()) { + runner.reloadFuncs = append(runner.reloadFuncs, reloadFunc) +} + +// runs all reload funcs to re-sync iptables rules +func (runner *runner) reload() { + glog.V(1).Infof("reloading iptables rules") + + for _, f := range runner.reloadFuncs { + f() + } +} + +// IsNotFoundError returns true if the error indicates "not found". It parses +// the error string looking for known values, which is imperfect but works in +// practice. +func IsNotFoundError(err error) bool { + es := err.Error() + if strings.Contains(es, "No such file or directory") { + return true + } + if strings.Contains(es, "No chain/target/match by that name") { + return true + } + return false +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/iptables/iptables_linux.go b/vendor/k8s.io/kubernetes/pkg/util/iptables/iptables_linux.go new file mode 100644 index 000000000..4f614cb52 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/iptables/iptables_linux.go @@ -0,0 +1,93 @@ +// +build linux + +/* +Copyright 2017 The Kubernetes 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 iptables + +import ( + "fmt" + "net" + "os" + "time" + + "golang.org/x/sys/unix" + "k8s.io/apimachinery/pkg/util/wait" +) + +type locker struct { + lock16 *os.File + lock14 *net.UnixListener +} + +func (l *locker) Close() { + if l.lock16 != nil { + l.lock16.Close() + } + if l.lock14 != nil { + l.lock14.Close() + } +} + +func grabIptablesLocks(lockfilePath string) (iptablesLocker, error) { + var err error + var success bool + + l := &locker{} + defer func(l *locker) { + // Clean up immediately on failure + if !success { + l.Close() + } + }(l) + + // Grab both 1.6.x and 1.4.x-style locks; we don't know what the + // iptables-restore version is if it doesn't support --wait, so we + // can't assume which lock method it'll use. + + // Roughly duplicate iptables 1.6.x xtables_lock() function. + l.lock16, err = os.OpenFile(lockfilePath, os.O_CREATE, 0600) + if err != nil { + return nil, fmt.Errorf("failed to open iptables lock %s: %v", lockfilePath, err) + } + + if err := wait.PollImmediate(200*time.Millisecond, 2*time.Second, func() (bool, error) { + if err := grabIptablesFileLock(l.lock16); err != nil { + return false, nil + } + return true, nil + }); err != nil { + return nil, fmt.Errorf("failed to acquire new iptables lock: %v", err) + } + + // Roughly duplicate iptables 1.4.x xtables_lock() function. + if err := wait.PollImmediate(200*time.Millisecond, 2*time.Second, func() (bool, error) { + l.lock14, err = net.ListenUnix("unix", &net.UnixAddr{Name: "@xtables", Net: "unix"}) + if err != nil { + return false, nil + } + return true, nil + }); err != nil { + return nil, fmt.Errorf("failed to acquire old iptables lock: %v", err) + } + + success = true + return l, nil +} + +func grabIptablesFileLock(f *os.File) error { + return unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/iptables/iptables_unsupported.go b/vendor/k8s.io/kubernetes/pkg/util/iptables/iptables_unsupported.go new file mode 100644 index 000000000..c6a5f0d7d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/iptables/iptables_unsupported.go @@ -0,0 +1,32 @@ +// +build !linux + +/* +Copyright 2017 The Kubernetes 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 iptables + +import ( + "fmt" + "os" +) + +func grabIptablesLocks(lockfilePath string) (iptablesLocker, error) { + return nil, fmt.Errorf("iptables unsupported on this platform") +} + +func grabIptablesFileLock(f *os.File) error { + return fmt.Errorf("iptables unsupported on this platform") +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/iptables/save_restore.go b/vendor/k8s.io/kubernetes/pkg/util/iptables/save_restore.go new file mode 100644 index 000000000..6f4eacaca --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/iptables/save_restore.go @@ -0,0 +1,110 @@ +/* +Copyright 2014 The Kubernetes 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 iptables + +import ( + "fmt" + "strings" +) + +// MakeChainLine return an iptables-save/restore formatted chain line given a Chain +func MakeChainLine(chain Chain) string { + return fmt.Sprintf(":%s - [0:0]", chain) +} + +// GetChainLines parses a table's iptables-save data to find chains in the table. +// It returns a map of iptables.Chain to string where the string is the chain line from the save (with counters etc). +func GetChainLines(table Table, save []byte) map[Chain]string { + chainsMap := make(map[Chain]string) + tablePrefix := "*" + string(table) + readIndex := 0 + // find beginning of table + for readIndex < len(save) { + line, n := ReadLine(readIndex, save) + readIndex = n + if strings.HasPrefix(line, tablePrefix) { + break + } + } + // parse table lines + for readIndex < len(save) { + line, n := ReadLine(readIndex, save) + readIndex = n + if len(line) == 0 { + continue + } + if strings.HasPrefix(line, "COMMIT") || strings.HasPrefix(line, "*") { + break + } else if strings.HasPrefix(line, "#") { + continue + } else if strings.HasPrefix(line, ":") && len(line) > 1 { + // We assume that the <line> contains space - chain lines have 3 fields, + // space delimited. If there is no space, this line will panic. + chain := Chain(line[1:strings.Index(line, " ")]) + chainsMap[chain] = line + } + } + return chainsMap +} + +func ReadLine(readIndex int, byteArray []byte) (string, int) { + currentReadIndex := readIndex + + // consume left spaces + for currentReadIndex < len(byteArray) { + if byteArray[currentReadIndex] == ' ' { + currentReadIndex++ + } else { + break + } + } + + // leftTrimIndex stores the left index of the line after the line is left-trimmed + leftTrimIndex := currentReadIndex + + // rightTrimIndex stores the right index of the line after the line is right-trimmed + // it is set to -1 since the correct value has not yet been determined. + rightTrimIndex := -1 + + for ; currentReadIndex < len(byteArray); currentReadIndex++ { + if byteArray[currentReadIndex] == ' ' { + // set rightTrimIndex + if rightTrimIndex == -1 { + rightTrimIndex = currentReadIndex + } + } else if (byteArray[currentReadIndex] == '\n') || (currentReadIndex == (len(byteArray) - 1)) { + // end of line or byte buffer is reached + if currentReadIndex <= leftTrimIndex { + return "", currentReadIndex + 1 + } + // set the rightTrimIndex + if rightTrimIndex == -1 { + rightTrimIndex = currentReadIndex + if currentReadIndex == (len(byteArray)-1) && (byteArray[currentReadIndex] != '\n') { + // ensure that the last character is part of the returned string, + // unless the last character is '\n' + rightTrimIndex = currentReadIndex + 1 + } + } + return string(byteArray[leftTrimIndex:rightTrimIndex]), currentReadIndex + 1 + } else { + // unset rightTrimIndex + rightTrimIndex = -1 + } + } + return "", currentReadIndex +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/doc.go b/vendor/k8s.io/kubernetes/pkg/util/mount/doc.go new file mode 100644 index 000000000..15179e53f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes 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 mount defines an interface to mounting filesystems. +package mount // import "k8s.io/kubernetes/pkg/util/mount" diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/fake.go b/vendor/k8s.io/kubernetes/pkg/util/mount/fake.go new file mode 100644 index 000000000..2b71fa0a7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/fake.go @@ -0,0 +1,173 @@ +/* +Copyright 2015 The Kubernetes 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 mount + +import ( + "path/filepath" + "sync" + + "github.com/golang/glog" +) + +// FakeMounter implements mount.Interface for tests. +type FakeMounter struct { + MountPoints []MountPoint + Log []FakeAction + // Some tests run things in parallel, make sure the mounter does not produce + // any golang's DATA RACE warnings. + mutex sync.Mutex +} + +var _ Interface = &FakeMounter{} + +// Values for FakeAction.Action +const FakeActionMount = "mount" +const FakeActionUnmount = "unmount" + +// FakeAction objects are logged every time a fake mount or unmount is called. +type FakeAction struct { + Action string // "mount" or "unmount" + Target string // applies to both mount and unmount actions + Source string // applies only to "mount" actions + FSType string // applies only to "mount" actions +} + +func (f *FakeMounter) ResetLog() { + f.mutex.Lock() + defer f.mutex.Unlock() + + f.Log = []FakeAction{} +} + +func (f *FakeMounter) Mount(source string, target string, fstype string, options []string) error { + f.mutex.Lock() + defer f.mutex.Unlock() + + // find 'bind' option + for _, option := range options { + if option == "bind" { + // This is a bind-mount. In order to mimic linux behaviour, we must + // use the original device of the bind-mount as the real source. + // E.g. when mounted /dev/sda like this: + // $ mount /dev/sda /mnt/test + // $ mount -o bind /mnt/test /mnt/bound + // then /proc/mount contains: + // /dev/sda /mnt/test + // /dev/sda /mnt/bound + // (and not /mnt/test /mnt/bound) + // I.e. we must use /dev/sda as source instead of /mnt/test in the + // bind mount. + for _, mnt := range f.MountPoints { + if source == mnt.Path { + source = mnt.Device + break + } + } + break + } + } + + // If target is a symlink, get its absolute path + absTarget, err := filepath.EvalSymlinks(target) + if err != nil { + absTarget = target + } + + f.MountPoints = append(f.MountPoints, MountPoint{Device: source, Path: absTarget, Type: fstype}) + glog.V(5).Infof("Fake mounter: mounted %s to %s", source, absTarget) + f.Log = append(f.Log, FakeAction{Action: FakeActionMount, Target: absTarget, Source: source, FSType: fstype}) + return nil +} + +func (f *FakeMounter) Unmount(target string) error { + f.mutex.Lock() + defer f.mutex.Unlock() + + // If target is a symlink, get its absolute path + absTarget, err := filepath.EvalSymlinks(target) + if err != nil { + absTarget = target + } + + newMountpoints := []MountPoint{} + for _, mp := range f.MountPoints { + if mp.Path == absTarget { + glog.V(5).Infof("Fake mounter: unmounted %s from %s", mp.Device, absTarget) + // Don't copy it to newMountpoints + continue + } + newMountpoints = append(newMountpoints, MountPoint{Device: mp.Device, Path: mp.Path, Type: mp.Type}) + } + f.MountPoints = newMountpoints + f.Log = append(f.Log, FakeAction{Action: FakeActionUnmount, Target: absTarget}) + return nil +} + +func (f *FakeMounter) List() ([]MountPoint, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + return f.MountPoints, nil +} + +func (f *FakeMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return (mp.Path == dir) +} + +func (f *FakeMounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(f, dir) +} + +func (f *FakeMounter) IsLikelyNotMountPoint(file string) (bool, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + // If file is a symlink, get its absolute path + absFile, err := filepath.EvalSymlinks(file) + if err != nil { + absFile = file + } + + for _, mp := range f.MountPoints { + if mp.Path == absFile { + glog.V(5).Infof("isLikelyNotMountPoint for %s: mounted %s, false", file, mp.Path) + return false, nil + } + } + glog.V(5).Infof("isLikelyNotMountPoint for %s: true", file) + return true, nil +} + +func (f *FakeMounter) DeviceOpened(pathname string) (bool, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + for _, mp := range f.MountPoints { + if mp.Device == pathname { + return true, nil + } + } + return false, nil +} + +func (f *FakeMounter) PathIsDevice(pathname string) (bool, error) { + return true, nil +} + +func (f *FakeMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return getDeviceNameFromMount(f, mountPath, pluginDir) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/mount.go b/vendor/k8s.io/kubernetes/pkg/util/mount/mount.go new file mode 100644 index 000000000..0c458d64b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/mount.go @@ -0,0 +1,245 @@ +/* +Copyright 2014 The Kubernetes 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. +*/ + +// TODO(thockin): This whole pkg is pretty linux-centric. As soon as we have +// an alternate platform, we will need to abstract further. +package mount + +import ( + "fmt" + "path" + "path/filepath" + "strings" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/util/exec" +) + +const ( + // Default mount command if mounter path is not specified + defaultMountCommand = "mount" + MountsInGlobalPDPath = "mounts" +) + +type Interface interface { + // Mount mounts source to target as fstype with given options. + Mount(source string, target string, fstype string, options []string) error + // Unmount unmounts given target. + Unmount(target string) error + // List returns a list of all mounted filesystems. This can be large. + // On some platforms, reading mounts is not guaranteed consistent (i.e. + // it could change between chunked reads). This is guaranteed to be + // consistent. + List() ([]MountPoint, error) + // IsMountPointMatch determines if the mountpoint matches the dir + IsMountPointMatch(mp MountPoint, dir string) bool + // IsNotMountPoint determines if a directory is a mountpoint. + // It should return ErrNotExist when the directory does not exist. + // IsNotMountPoint is more expensive than IsLikelyNotMountPoint. + // IsNotMountPoint detects bind mounts in linux. + // IsNotMountPoint enumerates all the mountpoints using List() and + // the list of mountpoints may be large, then it uses + // IsMountPointMatch to evaluate whether the directory is a mountpoint + IsNotMountPoint(file string) (bool, error) + // IsLikelyNotMountPoint uses heuristics to determine if a directory + // is a mountpoint. + // It should return ErrNotExist when the directory does not exist. + // IsLikelyNotMountPoint does NOT properly detect all mountpoint types + // most notably linux bind mounts. + IsLikelyNotMountPoint(file string) (bool, error) + // DeviceOpened determines if the device is in use elsewhere + // on the system, i.e. still mounted. + DeviceOpened(pathname string) (bool, error) + // PathIsDevice determines if a path is a device. + PathIsDevice(pathname string) (bool, error) + // GetDeviceNameFromMount finds the device name by checking the mount path + // to get the global mount path which matches its plugin directory + GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) +} + +// Compile-time check to ensure all Mounter implementations satisfy +// the mount interface +var _ Interface = &Mounter{} + +// This represents a single line in /proc/mounts or /etc/fstab. +type MountPoint struct { + Device string + Path string + Type string + Opts []string + Freq int + Pass int +} + +// SafeFormatAndMount probes a device to see if it is formatted. +// Namely it checks to see if a file system is present. If so it +// mounts it otherwise the device is formatted first then mounted. +type SafeFormatAndMount struct { + Interface + Runner exec.Interface +} + +// FormatAndMount formats the given disk, if needed, and mounts it. +// That is if the disk is not formatted and it is not being mounted as +// read-only it will format it first then mount it. Otherwise, if the +// disk is already formatted or it is being mounted as read-only, it +// will be mounted without formatting. +func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string, fstype string, options []string) error { + // Don't attempt to format if mounting as readonly. Go straight to mounting. + for _, option := range options { + if option == "ro" { + return mounter.Interface.Mount(source, target, fstype, options) + } + } + return mounter.formatAndMount(source, target, fstype, options) +} + +// New returns a mount.Interface for the current system. +// It provides options to override the default mounter behavior. +// mounterPath allows using an alternative to `/bin/mount` for mounting. +func New(mounterPath string) Interface { + return &Mounter{ + mounterPath: mounterPath, + } +} + +// GetMountRefs finds all other references to the device referenced +// by mountPath; returns a list of paths. +func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { + mps, err := mounter.List() + if err != nil { + return nil, err + } + + // Find the device name. + deviceName := "" + // If mountPath is symlink, need get its target path. + slTarget, err := filepath.EvalSymlinks(mountPath) + if err != nil { + slTarget = mountPath + } + for i := range mps { + if mps[i].Path == slTarget { + deviceName = mps[i].Device + break + } + } + + // Find all references to the device. + var refs []string + if deviceName == "" { + glog.Warningf("could not determine device for path: %q", mountPath) + } else { + for i := range mps { + if mps[i].Device == deviceName && mps[i].Path != slTarget { + refs = append(refs, mps[i].Path) + } + } + } + return refs, nil +} + +// GetDeviceNameFromMount: given a mnt point, find the device from /proc/mounts +// returns the device name, reference count, and error code +func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, error) { + mps, err := mounter.List() + if err != nil { + return "", 0, err + } + + // Find the device name. + // FIXME if multiple devices mounted on the same mount path, only the first one is returned + device := "" + // If mountPath is symlink, need get its target path. + slTarget, err := filepath.EvalSymlinks(mountPath) + if err != nil { + slTarget = mountPath + } + for i := range mps { + if mps[i].Path == slTarget { + device = mps[i].Device + break + } + } + + // Find all references to the device. + refCount := 0 + for i := range mps { + if mps[i].Device == device { + refCount++ + } + } + return device, refCount, nil +} + +// getDeviceNameFromMount find the device name from /proc/mounts in which +// the mount path reference should match the given plugin directory. In case no mount path reference +// matches, returns the volume name taken from its given mountPath +func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { + refs, err := GetMountRefs(mounter, mountPath) + if err != nil { + glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) + return "", err + } + if len(refs) == 0 { + glog.V(4).Infof("Directory %s is not mounted", mountPath) + return "", fmt.Errorf("directory %s is not mounted", mountPath) + } + basemountPath := path.Join(pluginDir, MountsInGlobalPDPath) + for _, ref := range refs { + if strings.HasPrefix(ref, basemountPath) { + volumeID, err := filepath.Rel(basemountPath, ref) + if err != nil { + glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) + return "", err + } + return volumeID, nil + } + } + + return path.Base(mountPath), nil +} + +// IsNotMountPoint determines if a directory is a mountpoint. +// It should return ErrNotExist when the directory does not exist. +// This method uses the List() of all mountpoints +// It is more extensive than IsLikelyNotMountPoint +// and it detects bind mounts in linux +func IsNotMountPoint(mounter Interface, file string) (bool, error) { + // IsLikelyNotMountPoint provides a quick check + // to determine whether file IS A mountpoint + notMnt, notMntErr := mounter.IsLikelyNotMountPoint(file) + if notMntErr != nil { + return notMnt, notMntErr + } + // identified as mountpoint, so return this fact + if notMnt == false { + return notMnt, nil + } + // check all mountpoints since IsLikelyNotMountPoint + // is not reliable for some mountpoint types + mountPoints, mountPointsErr := mounter.List() + if mountPointsErr != nil { + return notMnt, mountPointsErr + } + for _, mp := range mountPoints { + if mounter.IsMountPointMatch(mp, file) { + notMnt = false + break + } + } + return notMnt, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go new file mode 100644 index 000000000..4c141ad5b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_linux.go @@ -0,0 +1,432 @@ +// +build linux + +/* +Copyright 2014 The Kubernetes 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 mount + +import ( + "bufio" + "fmt" + "hash/fnv" + "io" + "os" + "os/exec" + "strconv" + "strings" + "syscall" + + "github.com/golang/glog" + "k8s.io/apimachinery/pkg/util/sets" + utilexec "k8s.io/kubernetes/pkg/util/exec" +) + +const ( + // How many times to retry for a consistent read of /proc/mounts. + maxListTries = 3 + // Number of fields per line in /proc/mounts as per the fstab man page. + expectedNumFieldsPerLine = 6 + // Location of the mount file to use + procMountsPath = "/proc/mounts" +) + +const ( + // 'fsck' found errors and corrected them + fsckErrorsCorrected = 1 + // 'fsck' found errors but exited without correcting them + fsckErrorsUncorrected = 4 +) + +// Mounter provides the default implementation of mount.Interface +// for the linux platform. This implementation assumes that the +// kubelet is running in the host's root mount namespace. +type Mounter struct { + mounterPath string +} + +// Mount mounts source to target as fstype with given options. 'source' and 'fstype' must +// be an emtpy string in case it's not required, e.g. for remount, or for auto filesystem +// type, where kernel handles fs type for you. The mount 'options' is a list of options, +// currently come from mount(8), e.g. "ro", "remount", "bind", etc. If no more option is +// required, call Mount with an empty string list or nil. +func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error { + // Path to mounter binary if containerized mounter is needed. Otherwise, it is set to empty. + // All Linux distros are expected to be shipped with a mount utility that an support bind mounts. + mounterPath := "" + bind, bindRemountOpts := isBind(options) + if bind { + err := doMount(mounterPath, defaultMountCommand, source, target, fstype, []string{"bind"}) + if err != nil { + return err + } + return doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts) + } + // The list of filesystems that require containerized mounter on GCI image cluster + fsTypesNeedMounter := sets.NewString("nfs", "glusterfs", "ceph", "cifs") + if fsTypesNeedMounter.Has(fstype) { + mounterPath = mounter.mounterPath + } + return doMount(mounterPath, defaultMountCommand, source, target, fstype, options) +} + +// isBind detects whether a bind mount is being requested and makes the remount options to +// use in case of bind mount, due to the fact that bind mount doesn't respect mount options. +// The list equals: +// options - 'bind' + 'remount' (no duplicate) +func isBind(options []string) (bool, []string) { + bindRemountOpts := []string{"remount"} + bind := false + + if len(options) != 0 { + for _, option := range options { + switch option { + case "bind": + bind = true + break + case "remount": + break + default: + bindRemountOpts = append(bindRemountOpts, option) + } + } + } + + return bind, bindRemountOpts +} + +// doMount runs the mount command. mounterPath is the path to mounter binary if containerized mounter is used. +func doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string) error { + mountArgs := makeMountArgs(source, target, fstype, options) + if len(mounterPath) > 0 { + mountArgs = append([]string{mountCmd}, mountArgs...) + mountCmd = mounterPath + } + + glog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgs) + command := exec.Command(mountCmd, mountArgs...) + output, err := command.CombinedOutput() + if err != nil { + glog.Errorf("Mount failed: %v\nMounting command: %s\nMounting arguments: %s %s %s %v\nOutput: %s\n", err, mountCmd, source, target, fstype, options, string(output)) + return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s %s %s %v\nOutput: %s\n", + err, mountCmd, source, target, fstype, options, string(output)) + } + return err +} + +// makeMountArgs makes the arguments to the mount(8) command. +func makeMountArgs(source, target, fstype string, options []string) []string { + // Build mount command as follows: + // mount [-t $fstype] [-o $options] [$source] $target + mountArgs := []string{} + if len(fstype) > 0 { + mountArgs = append(mountArgs, "-t", fstype) + } + if len(options) > 0 { + mountArgs = append(mountArgs, "-o", strings.Join(options, ",")) + } + if len(source) > 0 { + mountArgs = append(mountArgs, source) + } + mountArgs = append(mountArgs, target) + + return mountArgs +} + +// Unmount unmounts the target. +func (mounter *Mounter) Unmount(target string) error { + glog.V(4).Infof("Unmounting %s", target) + command := exec.Command("umount", target) + output, err := command.CombinedOutput() + if err != nil { + return fmt.Errorf("Unmount failed: %v\nUnmounting arguments: %s\nOutput: %s\n", err, target, string(output)) + } + return nil +} + +// List returns a list of all mounted filesystems. +func (*Mounter) List() ([]MountPoint, error) { + return listProcMounts(procMountsPath) +} + +func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool { + deletedDir := fmt.Sprintf("%s\\040(deleted)", dir) + return ((mp.Path == dir) || (mp.Path == deletedDir)) +} + +func (mounter *Mounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(mounter, dir) +} + +// IsLikelyNotMountPoint determines if a directory is not a mountpoint. +// It is fast but not necessarily ALWAYS correct. If the path is in fact +// a bind mount from one part of a mount to another it will not be detected. +// mkdir /tmp/a /tmp/b; mount --bin /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b") +// will return true. When in fact /tmp/b is a mount point. If this situation +// if of interest to you, don't use this function... +func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { + stat, err := os.Stat(file) + if err != nil { + return true, err + } + rootStat, err := os.Lstat(file + "/..") + if err != nil { + return true, err + } + // If the directory has a different device as parent, then it is a mountpoint. + if stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev { + return false, nil + } + + return true, nil +} + +// DeviceOpened checks if block device in use by calling Open with O_EXCL flag. +// If pathname is not a device, log and return false with nil error. +// If open returns errno EBUSY, return true with nil error. +// If open returns nil, return false with nil error. +// Otherwise, return false with error +func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { + return exclusiveOpenFailsOnDevice(pathname) +} + +// PathIsDevice uses FileInfo returned from os.Stat to check if path refers +// to a device. +func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { + return pathIsDevice(pathname) +} + +func exclusiveOpenFailsOnDevice(pathname string) (bool, error) { + isDevice, err := pathIsDevice(pathname) + if err != nil { + return false, fmt.Errorf( + "PathIsDevice failed for path %q: %v", + pathname, + err) + } + if !isDevice { + glog.Errorf("Path %q is not refering to a device.", pathname) + return false, nil + } + fd, errno := syscall.Open(pathname, syscall.O_RDONLY|syscall.O_EXCL, 0) + // If the device is in use, open will return an invalid fd. + // When this happens, it is expected that Close will fail and throw an error. + defer syscall.Close(fd) + if errno == nil { + // device not in use + return false, nil + } else if errno == syscall.EBUSY { + // device is in use + return true, nil + } + // error during call to Open + return false, errno +} + +func pathIsDevice(pathname string) (bool, error) { + finfo, err := os.Stat(pathname) + if os.IsNotExist(err) { + return false, nil + } + // err in call to os.Stat + if err != nil { + return false, err + } + // path refers to a device + if finfo.Mode()&os.ModeDevice != 0 { + return true, nil + } + // path does not refer to device + return false, nil +} + +//GetDeviceNameFromMount: given a mount point, find the device name from its global mount point +func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return getDeviceNameFromMount(mounter, mountPath, pluginDir) +} + +func listProcMounts(mountFilePath string) ([]MountPoint, error) { + hash1, err := readProcMounts(mountFilePath, nil) + if err != nil { + return nil, err + } + + for i := 0; i < maxListTries; i++ { + mps := []MountPoint{} + hash2, err := readProcMounts(mountFilePath, &mps) + if err != nil { + return nil, err + } + if hash1 == hash2 { + // Success + return mps, nil + } + hash1 = hash2 + } + return nil, fmt.Errorf("failed to get a consistent snapshot of %v after %d tries", mountFilePath, maxListTries) +} + +// readProcMounts reads the given mountFilePath (normally /proc/mounts) and produces a hash +// of the contents. If the out argument is not nil, this fills it with MountPoint structs. +func readProcMounts(mountFilePath string, out *[]MountPoint) (uint32, error) { + file, err := os.Open(mountFilePath) + if err != nil { + return 0, err + } + defer file.Close() + return readProcMountsFrom(file, out) +} + +func readProcMountsFrom(file io.Reader, out *[]MountPoint) (uint32, error) { + hash := fnv.New32a() + scanner := bufio.NewReader(file) + for { + line, err := scanner.ReadString('\n') + if err == io.EOF { + break + } + fields := strings.Fields(line) + if len(fields) != expectedNumFieldsPerLine { + return 0, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line) + } + + fmt.Fprintf(hash, "%s", line) + + if out != nil { + mp := MountPoint{ + Device: fields[0], + Path: fields[1], + Type: fields[2], + Opts: strings.Split(fields[3], ","), + } + + freq, err := strconv.Atoi(fields[4]) + if err != nil { + return 0, err + } + mp.Freq = freq + + pass, err := strconv.Atoi(fields[5]) + if err != nil { + return 0, err + } + mp.Pass = pass + + *out = append(*out, mp) + } + } + return hash.Sum32(), nil +} + +// formatAndMount uses unix utils to format and mount the given disk +func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { + options = append(options, "defaults") + + // Run fsck on the disk to fix repairable issues + glog.V(4).Infof("Checking for issues with fsck on disk: %s", source) + args := []string{"-a", source} + cmd := mounter.Runner.Command("fsck", args...) + out, err := cmd.CombinedOutput() + if err != nil { + ee, isExitError := err.(utilexec.ExitError) + switch { + case err == utilexec.ErrExecutableNotFound: + glog.Warningf("'fsck' not found on system; continuing mount without running 'fsck'.") + case isExitError && ee.ExitStatus() == fsckErrorsCorrected: + glog.Infof("Device %s has errors which were corrected by fsck.", source) + case isExitError && ee.ExitStatus() == fsckErrorsUncorrected: + return fmt.Errorf("'fsck' found errors on device %s but could not correct them: %s.", source, string(out)) + case isExitError && ee.ExitStatus() > fsckErrorsUncorrected: + glog.Infof("`fsck` error %s", string(out)) + } + } + + // Try to mount the disk + glog.V(4).Infof("Attempting to mount disk: %s %s %s", fstype, source, target) + mountErr := mounter.Interface.Mount(source, target, fstype, options) + if mountErr != nil { + // Mount failed. This indicates either that the disk is unformatted or + // it contains an unexpected filesystem. + existingFormat, err := mounter.getDiskFormat(source) + if err != nil { + return err + } + if existingFormat == "" { + // Disk is unformatted so format it. + args = []string{source} + // Use 'ext4' as the default + if len(fstype) == 0 { + fstype = "ext4" + } + + if fstype == "ext4" || fstype == "ext3" { + args = []string{"-F", source} + } + glog.Infof("Disk %q appears to be unformatted, attempting to format as type: %q with options: %v", source, fstype, args) + cmd := mounter.Runner.Command("mkfs."+fstype, args...) + _, err := cmd.CombinedOutput() + if err == nil { + // the disk has been formatted successfully try to mount it again. + glog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target) + return mounter.Interface.Mount(source, target, fstype, options) + } + glog.Errorf("format of disk %q failed: type:(%q) target:(%q) options:(%q)error:(%v)", source, fstype, target, options, err) + return err + } else { + // Disk is already formatted and failed to mount + if len(fstype) == 0 || fstype == existingFormat { + // This is mount error + return mountErr + } else { + // Block device is formatted with unexpected filesystem, let the user know + return fmt.Errorf("failed to mount the volume as %q, it already contains %s. Mount error: %v", fstype, existingFormat, mountErr) + } + } + } + return mountErr +} + +// diskLooksUnformatted uses 'lsblk' to see if the given disk is unformated +func (mounter *SafeFormatAndMount) getDiskFormat(disk string) (string, error) { + args := []string{"-n", "-o", "FSTYPE", disk} + cmd := mounter.Runner.Command("lsblk", args...) + glog.V(4).Infof("Attempting to determine if disk %q is formatted using lsblk with args: (%v)", disk, args) + dataOut, err := cmd.CombinedOutput() + output := string(dataOut) + glog.V(4).Infof("Output: %q", output) + + if err != nil { + glog.Errorf("Could not determine if disk %q is formatted (%v)", disk, err) + return "", err + } + + // Split lsblk output into lines. Unformatted devices should contain only + // "\n". Beware of "\n\n", that's a device with one empty partition. + output = strings.TrimSuffix(output, "\n") // Avoid last empty line + lines := strings.Split(output, "\n") + if lines[0] != "" { + // The device is formatted + return lines[0], nil + } + + if len(lines) == 1 { + // The device is unformatted and has no dependent devices + return "", nil + } + + // The device has dependent devices, most probably partitions (LVM, LUKS + // and MD RAID are reported as FSTYPE and caught above). + return "unknown data, probably partitions", nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/mount_unsupported.go b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_unsupported.go new file mode 100644 index 000000000..632ad0606 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/mount_unsupported.go @@ -0,0 +1,67 @@ +// +build !linux + +/* +Copyright 2014 The Kubernetes 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 mount + +type Mounter struct { + mounterPath string +} + +func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error { + return nil +} + +func (mounter *Mounter) Unmount(target string) error { + return nil +} + +func (mounter *Mounter) List() ([]MountPoint, error) { + return []MountPoint{}, nil +} + +func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return (mp.Path == dir) +} + +func (mounter *Mounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(mounter, dir) +} + +func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { + return true, nil +} + +func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return "", nil +} + +func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { + return false, nil +} + +func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { + return true, nil +} + +func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error { + return nil +} + +func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) { + return true, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount.go b/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount.go new file mode 100644 index 000000000..4af8ef0d8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount.go @@ -0,0 +1,241 @@ +// +build linux + +/* +Copyright 2014 The Kubernetes 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 mount + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/util/exec" +) + +// NsenterMounter is part of experimental support for running the kubelet +// in a container. Currently, all docker containers receive their own mount +// namespaces. NsenterMounter works by executing nsenter to run commands in +// the host's mount namespace. +// +// NsenterMounter requires: +// +// 1. Docker >= 1.6 due to the dependency on the slave propagation mode +// of the bind-mount of the kubelet root directory in the container. +// Docker 1.5 used a private propagation mode for bind-mounts, so mounts +// performed in the host's mount namespace do not propagate out to the +// bind-mount in this docker version. +// 2. The host's root filesystem must be available at /rootfs +// 3. The nsenter binary must be on the Kubelet process' PATH in the container's +// filesystem. +// 4. The Kubelet process must have CAP_SYS_ADMIN (required by nsenter); at +// the present, this effectively means that the kubelet is running in a +// privileged container. +// 5. The volume path used by the Kubelet must be the same inside and outside +// the container and be writable by the container (to initialize volume) +// contents. TODO: remove this requirement. +// 6. The host image must have mount, findmnt, and umount binaries in /bin, +// /usr/sbin, or /usr/bin +// +// For more information about mount propagation modes, see: +// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt +type NsenterMounter struct { + // a map of commands to their paths on the host filesystem + paths map[string]string +} + +func NewNsenterMounter() *NsenterMounter { + m := &NsenterMounter{ + paths: map[string]string{ + "mount": "", + "findmnt": "", + "umount": "", + }, + } + // search for the mount command in other locations besides /usr/bin + for binary := range m.paths { + // default to root + m.paths[binary] = filepath.Join("/", binary) + for _, path := range []string{"/bin", "/usr/sbin", "/usr/bin"} { + binPath := filepath.Join(path, binary) + if _, err := os.Stat(filepath.Join(hostRootFsPath, binPath)); err != nil { + continue + } + m.paths[binary] = binPath + break + } + // TODO: error, so that the kubelet can stop if the mounts don't exist + } + return m +} + +// NsenterMounter implements mount.Interface +var _ = Interface(&NsenterMounter{}) + +const ( + hostRootFsPath = "/rootfs" + hostProcMountsPath = "/rootfs/proc/1/mounts" + nsenterPath = "nsenter" +) + +// Mount runs mount(8) in the host's root mount namespace. Aside from this +// aspect, Mount has the same semantics as the mounter returned by mount.New() +func (n *NsenterMounter) Mount(source string, target string, fstype string, options []string) error { + bind, bindRemountOpts := isBind(options) + + if bind { + err := n.doNsenterMount(source, target, fstype, []string{"bind"}) + if err != nil { + return err + } + return n.doNsenterMount(source, target, fstype, bindRemountOpts) + } + + return n.doNsenterMount(source, target, fstype, options) +} + +// doNsenterMount nsenters the host's mount namespace and performs the +// requested mount. +func (n *NsenterMounter) doNsenterMount(source, target, fstype string, options []string) error { + glog.V(5).Infof("nsenter Mounting %s %s %s %v", source, target, fstype, options) + args := n.makeNsenterArgs(source, target, fstype, options) + + glog.V(5).Infof("Mount command: %v %v", nsenterPath, args) + exec := exec.New() + outputBytes, err := exec.Command(nsenterPath, args...).CombinedOutput() + if len(outputBytes) != 0 { + glog.V(5).Infof("Output of mounting %s to %s: %v", source, target, string(outputBytes)) + } + + return err +} + +// makeNsenterArgs makes a list of argument to nsenter in order to do the +// requested mount. +func (n *NsenterMounter) makeNsenterArgs(source, target, fstype string, options []string) []string { + nsenterArgs := []string{ + "--mount=/rootfs/proc/1/ns/mnt", + "--", + n.absHostPath("mount"), + } + + args := makeMountArgs(source, target, fstype, options) + + return append(nsenterArgs, args...) +} + +// Unmount runs umount(8) in the host's mount namespace. +func (n *NsenterMounter) Unmount(target string) error { + args := []string{ + "--mount=/rootfs/proc/1/ns/mnt", + "--", + n.absHostPath("umount"), + target, + } + + glog.V(5).Infof("Unmount command: %v %v", nsenterPath, args) + exec := exec.New() + outputBytes, err := exec.Command(nsenterPath, args...).CombinedOutput() + if len(outputBytes) != 0 { + glog.V(5).Infof("Output of unmounting %s: %v", target, string(outputBytes)) + } + + return err +} + +// List returns a list of all mounted filesystems in the host's mount namespace. +func (*NsenterMounter) List() ([]MountPoint, error) { + return listProcMounts(hostProcMountsPath) +} + +func (m *NsenterMounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(m, dir) +} + +func (*NsenterMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + deletedDir := fmt.Sprintf("%s\\040(deleted)", dir) + return ((mp.Path == dir) || (mp.Path == deletedDir)) +} + +// IsLikelyNotMountPoint determines whether a path is a mountpoint by calling findmnt +// in the host's root mount namespace. +func (n *NsenterMounter) IsLikelyNotMountPoint(file string) (bool, error) { + file, err := filepath.Abs(file) + if err != nil { + return true, err + } + + // Check the directory exists + if _, err = os.Stat(file); os.IsNotExist(err) { + glog.V(5).Infof("findmnt: directory %s does not exist", file) + return true, err + } + // Add --first-only option: since we are testing for the absence of a mountpoint, it is sufficient to get only + // the first of multiple possible mountpoints using --first-only. + // Also add fstype output to make sure that the output of target file will give the full path + // TODO: Need more refactoring for this function. Track the solution with issue #26996 + args := []string{"--mount=/rootfs/proc/1/ns/mnt", "--", n.absHostPath("findmnt"), "-o", "target,fstype", "--noheadings", "--first-only", "--target", file} + glog.V(5).Infof("findmnt command: %v %v", nsenterPath, args) + + exec := exec.New() + out, err := exec.Command(nsenterPath, args...).CombinedOutput() + if err != nil { + glog.V(2).Infof("Failed findmnt command for path %s: %v", file, err) + // Different operating systems behave differently for paths which are not mount points. + // On older versions (e.g. 2.20.1) we'd get error, on newer ones (e.g. 2.26.2) we'd get "/". + // It's safer to assume that it's not a mount point. + return true, nil + } + mountTarget := strings.Split(string(out), " ")[0] + mountTarget = strings.TrimSuffix(mountTarget, "\n") + glog.V(5).Infof("IsLikelyNotMountPoint findmnt output for path %s: %v:", file, mountTarget) + + if mountTarget == file { + glog.V(5).Infof("IsLikelyNotMountPoint: %s is a mount point", file) + return false, nil + } + glog.V(5).Infof("IsLikelyNotMountPoint: %s is not a mount point", file) + return true, nil +} + +// DeviceOpened checks if block device in use by calling Open with O_EXCL flag. +// Returns true if open returns errno EBUSY, and false if errno is nil. +// Returns an error if errno is any error other than EBUSY. +// Returns with error if pathname is not a device. +func (n *NsenterMounter) DeviceOpened(pathname string) (bool, error) { + return exclusiveOpenFailsOnDevice(pathname) +} + +// PathIsDevice uses FileInfo returned from os.Stat to check if path refers +// to a device. +func (n *NsenterMounter) PathIsDevice(pathname string) (bool, error) { + return pathIsDevice(pathname) +} + +//GetDeviceNameFromMount given a mount point, find the volume id from checking /proc/mounts +func (n *NsenterMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return getDeviceNameFromMount(n, mountPath, pluginDir) +} + +func (n *NsenterMounter) absHostPath(command string) string { + path, ok := n.paths[command] + if !ok { + return command + } + return path +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount_unsupported.go b/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount_unsupported.go new file mode 100644 index 000000000..e955e1b78 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/mount/nsenter_mount_unsupported.go @@ -0,0 +1,63 @@ +// +build !linux + +/* +Copyright 2014 The Kubernetes 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 mount + +type NsenterMounter struct{} + +func NewNsenterMounter() *NsenterMounter { + return &NsenterMounter{} +} + +var _ = Interface(&NsenterMounter{}) + +func (*NsenterMounter) Mount(source string, target string, fstype string, options []string) error { + return nil +} + +func (*NsenterMounter) Unmount(target string) error { + return nil +} + +func (*NsenterMounter) List() ([]MountPoint, error) { + return []MountPoint{}, nil +} + +func (m *NsenterMounter) IsNotMountPoint(dir string) (bool, error) { + return IsNotMountPoint(m, dir) +} + +func (*NsenterMounter) IsMountPointMatch(mp MountPoint, dir string) bool { + return (mp.Path == dir) +} + +func (*NsenterMounter) IsLikelyNotMountPoint(file string) (bool, error) { + return true, nil +} + +func (*NsenterMounter) DeviceOpened(pathname string) (bool, error) { + return false, nil +} + +func (*NsenterMounter) PathIsDevice(pathname string) (bool, error) { + return true, nil +} + +func (*NsenterMounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) { + return "", nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/net/sets/doc.go b/vendor/k8s.io/kubernetes/pkg/util/net/sets/doc.go new file mode 100644 index 000000000..8414f74ac --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/net/sets/doc.go @@ -0,0 +1,28 @@ +/* +Copyright 2016 The Kubernetes 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. +*/ + +// This package contains hand-coded set implementations that should be similar +// to the autogenerated ones in pkg/util/sets. +// We can't simply use net.IPNet as a map-key in Go (because it contains a +// []byte). +// We could use the same workaround we use here (a string representation as the +// key) to autogenerate sets. If we do that, or decide on an alternate +// approach, we should replace the implementations in this package with the +// autogenerated versions. +// It is expected that callers will alias this import as "netsets" i.e. import +// netsets "k8s.io/kubernetes/pkg/util/net/sets" + +package sets diff --git a/vendor/k8s.io/kubernetes/pkg/util/net/sets/ipnet.go b/vendor/k8s.io/kubernetes/pkg/util/net/sets/ipnet.go new file mode 100644 index 000000000..5b6fe933f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/net/sets/ipnet.go @@ -0,0 +1,119 @@ +/* +Copyright 2016 The Kubernetes 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 sets + +import ( + "net" + "strings" +) + +type IPNet map[string]*net.IPNet + +func ParseIPNets(specs ...string) (IPNet, error) { + ipnetset := make(IPNet) + for _, spec := range specs { + spec = strings.TrimSpace(spec) + _, ipnet, err := net.ParseCIDR(spec) + if err != nil { + return nil, err + } + k := ipnet.String() // In case of normalization + ipnetset[k] = ipnet + } + return ipnetset, nil +} + +// Insert adds items to the set. +func (s IPNet) Insert(items ...*net.IPNet) { + for _, item := range items { + s[item.String()] = item + } +} + +// Delete removes all items from the set. +func (s IPNet) Delete(items ...*net.IPNet) { + for _, item := range items { + delete(s, item.String()) + } +} + +// Has returns true if and only if item is contained in the set. +func (s IPNet) Has(item *net.IPNet) bool { + _, contained := s[item.String()] + return contained +} + +// HasAll returns true if and only if all items are contained in the set. +func (s IPNet) HasAll(items ...*net.IPNet) bool { + for _, item := range items { + if !s.Has(item) { + return false + } + } + return true +} + +// Difference returns a set of objects that are not in s2 +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s IPNet) Difference(s2 IPNet) IPNet { + result := make(IPNet) + for k, i := range s { + _, found := s2[k] + if found { + continue + } + result[k] = i + } + return result +} + +// StringSlice returns a []string with the String representation of each element in the set. +// Order is undefined. +func (s IPNet) StringSlice() []string { + a := make([]string, 0, len(s)) + for k := range s { + a = append(a, k) + } + return a +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 IPNet) IsSuperset(s2 IPNet) bool { + for k := range s2 { + _, found := s1[k] + if !found { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 IPNet) Equal(s2 IPNet) bool { + return len(s1) == len(s2) && s1.IsSuperset(s2) +} + +// Len returns the size of the set. +func (s IPNet) Len() int { + return len(s) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/parsers/parsers.go b/vendor/k8s.io/kubernetes/pkg/util/parsers/parsers.go new file mode 100644 index 000000000..4e70cc682 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/parsers/parsers.go @@ -0,0 +1,54 @@ +/* +Copyright 2015 The Kubernetes 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 parsers + +import ( + "fmt" + + dockerref "github.com/docker/distribution/reference" +) + +const ( + DefaultImageTag = "latest" +) + +// ParseImageName parses a docker image string into three parts: repo, tag and digest. +// If both tag and digest are empty, a default image tag will be returned. +func ParseImageName(image string) (string, string, string, error) { + named, err := dockerref.ParseNamed(image) + if err != nil { + return "", "", "", fmt.Errorf("couldn't parse image name: %v", err) + } + + repoToPull := named.Name() + var tag, digest string + + tagged, ok := named.(dockerref.Tagged) + if ok { + tag = tagged.Tag() + } + + digested, ok := named.(dockerref.Digested) + if ok { + digest = digested.Digest().String() + } + // If no tag was specified, use the default "latest". + if len(tag) == 0 && len(digest) == 0 { + tag = DefaultImageTag + } + return repoToPull, tag, digest, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/sysctl/sysctl.go b/vendor/k8s.io/kubernetes/pkg/util/sysctl/sysctl.go new file mode 100644 index 000000000..5c01dd88e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/sysctl/sysctl.go @@ -0,0 +1,78 @@ +/* +Copyright 2015 The Kubernetes 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 sysctl + +import ( + "io/ioutil" + "path" + "strconv" + "strings" +) + +const ( + sysctlBase = "/proc/sys" + VmOvercommitMemory = "vm/overcommit_memory" + VmPanicOnOOM = "vm/panic_on_oom" + KernelPanic = "kernel/panic" + KernelPanicOnOops = "kernel/panic_on_oops" + RootMaxKeys = "kernel/keys/root_maxkeys" + RootMaxBytes = "kernel/keys/root_maxbytes" + + VmOvercommitMemoryAlways = 1 // kernel performs no memory over-commit handling + VmPanicOnOOMInvokeOOMKiller = 0 // kernel calls the oom_killer function when OOM occurs + + KernelPanicOnOopsAlways = 1 // kernel panics on kernel oops + KernelPanicRebootTimeout = 10 // seconds after a panic for the kernel to reboot + + RootMaxKeysSetting = 1000000 // Needed since docker creates a new key per container + RootMaxBytesSetting = RootMaxKeysSetting * 25 // allocate 25 bytes per key * number of MaxKeys +) + +// An injectable interface for running sysctl commands. +type Interface interface { + // GetSysctl returns the value for the specified sysctl setting + GetSysctl(sysctl string) (int, error) + // SetSysctl modifies the specified sysctl flag to the new value + SetSysctl(sysctl string, newVal int) error +} + +// New returns a new Interface for accessing sysctl +func New() Interface { + return &procSysctl{} +} + +// procSysctl implements Interface by reading and writing files under /proc/sys +type procSysctl struct { +} + +// GetSysctl returns the value for the specified sysctl setting +func (_ *procSysctl) GetSysctl(sysctl string) (int, error) { + data, err := ioutil.ReadFile(path.Join(sysctlBase, sysctl)) + if err != nil { + return -1, err + } + val, err := strconv.Atoi(strings.Trim(string(data), " \n")) + if err != nil { + return -1, err + } + return val, nil +} + +// SetSysctl modifies the specified sysctl flag to the new value +func (_ *procSysctl) SetSysctl(sysctl string, newVal int) error { + return ioutil.WriteFile(path.Join(sysctlBase, sysctl), []byte(strconv.Itoa(newVal)), 0640) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/template.go b/vendor/k8s.io/kubernetes/pkg/util/template.go new file mode 100644 index 000000000..d09d7dc86 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/template.go @@ -0,0 +1,48 @@ +/* +Copyright 2015 The Kubernetes 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 util + +import ( + "bytes" + "go/doc" + "io" + "strings" + "text/template" +) + +func wrap(indent string, s string) string { + var buf bytes.Buffer + doc.ToText(&buf, s, indent, indent+" ", 80-len(indent)) + return buf.String() +} + +// ExecuteTemplate executes templateText with data and output written to w. +func ExecuteTemplate(w io.Writer, templateText string, data interface{}) error { + t := template.New("top") + t.Funcs(template.FuncMap{ + "trim": strings.TrimSpace, + "wrap": wrap, + }) + template.Must(t.Parse(templateText)) + return t.Execute(w, data) +} + +func ExecuteTemplateToString(templateText string, data interface{}) (string, error) { + b := bytes.Buffer{} + err := ExecuteTemplate(&b, templateText, data) + return b.String(), err +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/term/resize.go b/vendor/k8s.io/kubernetes/pkg/util/term/resize.go new file mode 100644 index 000000000..7ca09a858 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/term/resize.go @@ -0,0 +1,132 @@ +/* +Copyright 2016 The Kubernetes 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 term + +import ( + "fmt" + + "github.com/docker/docker/pkg/term" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/remotecommand" +) + +// GetSize returns the current size of the user's terminal. If it isn't a terminal, +// nil is returned. +func (t TTY) GetSize() *remotecommand.TerminalSize { + outFd, isTerminal := term.GetFdInfo(t.Out) + if !isTerminal { + return nil + } + return GetSize(outFd) +} + +// GetSize returns the current size of the terminal associated with fd. +func GetSize(fd uintptr) *remotecommand.TerminalSize { + winsize, err := term.GetWinsize(fd) + if err != nil { + runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err)) + return nil + } + + return &remotecommand.TerminalSize{Width: winsize.Width, Height: winsize.Height} +} + +// MonitorSize monitors the terminal's size. It returns a TerminalSizeQueue primed with +// initialSizes, or nil if there's no TTY present. +func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecommand.TerminalSizeQueue { + outFd, isTerminal := term.GetFdInfo(t.Out) + if !isTerminal { + return nil + } + + t.sizeQueue = &sizeQueue{ + t: *t, + // make it buffered so we can send the initial terminal sizes without blocking, prior to starting + // the streaming below + resizeChan: make(chan remotecommand.TerminalSize, len(initialSizes)), + stopResizing: make(chan struct{}), + } + + t.sizeQueue.monitorSize(outFd, initialSizes...) + + return t.sizeQueue +} + +// sizeQueue implements remotecommand.TerminalSizeQueue +type sizeQueue struct { + t TTY + // resizeChan receives a Size each time the user's terminal is resized. + resizeChan chan remotecommand.TerminalSize + stopResizing chan struct{} +} + +// make sure sizeQueue implements the resize.TerminalSizeQueue interface +var _ remotecommand.TerminalSizeQueue = &sizeQueue{} + +// monitorSize primes resizeChan with initialSizes and then monitors for resize events. With each +// new event, it sends the current terminal size to resizeChan. +func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.TerminalSize) { + // send the initial sizes + for i := range initialSizes { + if initialSizes[i] != nil { + s.resizeChan <- *initialSizes[i] + } + } + + resizeEvents := make(chan remotecommand.TerminalSize, 1) + + monitorResizeEvents(outFd, resizeEvents, s.stopResizing) + + // listen for resize events in the background + go func() { + defer runtime.HandleCrash() + + for { + select { + case size, ok := <-resizeEvents: + if !ok { + return + } + + select { + // try to send the size to resizeChan, but don't block + case s.resizeChan <- size: + // send successful + default: + // unable to send / no-op + } + case <-s.stopResizing: + return + } + } + }() +} + +// Next returns the new terminal size after the terminal has been resized. It returns nil when +// monitoring has been stopped. +func (s *sizeQueue) Next() *remotecommand.TerminalSize { + size, ok := <-s.resizeChan + if !ok { + return nil + } + return &size +} + +// stop stops the background goroutine that is monitoring for terminal resizes. +func (s *sizeQueue) stop() { + close(s.stopResizing) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/term/resizeevents.go b/vendor/k8s.io/kubernetes/pkg/util/term/resizeevents.go new file mode 100644 index 000000000..75e9690df --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/term/resizeevents.go @@ -0,0 +1,61 @@ +// +build !windows + +/* +Copyright 2016 The Kubernetes 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 term + +import ( + "os" + "os/signal" + "syscall" + + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/remotecommand" +) + +// monitorResizeEvents spawns a goroutine that waits for SIGWINCH signals (these indicate the +// terminal has resized). After receiving a SIGWINCH, this gets the terminal size and tries to send +// it to the resizeEvents channel. The goroutine stops when the stop channel is closed. +func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) { + go func() { + defer runtime.HandleCrash() + + winch := make(chan os.Signal, 1) + signal.Notify(winch, syscall.SIGWINCH) + defer signal.Stop(winch) + + for { + select { + case <-winch: + size := GetSize(fd) + if size == nil { + return + } + + // try to send size + select { + case resizeEvents <- *size: + // success + default: + // not sent + } + case <-stop: + return + } + } + }() +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/term/resizeevents_windows.go b/vendor/k8s.io/kubernetes/pkg/util/term/resizeevents_windows.go new file mode 100644 index 000000000..adccf8734 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/term/resizeevents_windows.go @@ -0,0 +1,62 @@ +/* +Copyright 2016 The Kubernetes 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 term + +import ( + "time" + + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/remotecommand" +) + +// monitorResizeEvents spawns a goroutine that periodically gets the terminal size and tries to send +// it to the resizeEvents channel if the size has changed. The goroutine stops when the stop channel +// is closed. +func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) { + go func() { + defer runtime.HandleCrash() + + size := GetSize(fd) + if size == nil { + return + } + lastSize := *size + + for { + // see if we need to stop running + select { + case <-stop: + return + default: + } + + size := GetSize(fd) + if size == nil { + return + } + + if size.Height != lastSize.Height || size.Width != lastSize.Width { + lastSize.Height = size.Height + lastSize.Width = size.Width + resizeEvents <- *size + } + + // sleep to avoid hot looping + time.Sleep(250 * time.Millisecond) + } + }() +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/term/setsize.go b/vendor/k8s.io/kubernetes/pkg/util/term/setsize.go new file mode 100644 index 000000000..8cccd431a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/term/setsize.go @@ -0,0 +1,29 @@ +// +build !windows + +/* +Copyright 2016 The Kubernetes 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 term + +import ( + "github.com/docker/docker/pkg/term" + "k8s.io/client-go/tools/remotecommand" +) + +// SetSize sets the terminal size associated with fd. +func SetSize(fd uintptr, size remotecommand.TerminalSize) error { + return term.SetWinsize(fd, &term.Winsize{Height: size.Height, Width: size.Width}) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/term/setsize_unsupported.go b/vendor/k8s.io/kubernetes/pkg/util/term/setsize_unsupported.go new file mode 100644 index 000000000..82220217a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/term/setsize_unsupported.go @@ -0,0 +1,28 @@ +// +build windows + +/* +Copyright 2016 The Kubernetes 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 term + +import ( + "k8s.io/client-go/tools/remotecommand" +) + +func SetSize(fd uintptr, size remotecommand.TerminalSize) error { + // NOP + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/term/term.go b/vendor/k8s.io/kubernetes/pkg/util/term/term.go new file mode 100644 index 000000000..58baee831 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/term/term.go @@ -0,0 +1,110 @@ +/* +Copyright 2016 The Kubernetes 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 term + +import ( + "io" + "os" + + "github.com/docker/docker/pkg/term" + + "k8s.io/kubernetes/pkg/util/interrupt" +) + +// SafeFunc is a function to be invoked by TTY. +type SafeFunc func() error + +// TTY helps invoke a function and preserve the state of the terminal, even if the process is +// terminated during execution. It also provides support for terminal resizing for remote command +// execution/attachment. +type TTY struct { + // In is a reader representing stdin. It is a required field. + In io.Reader + // Out is a writer representing stdout. It must be set to support terminal resizing. It is an + // optional field. + Out io.Writer + // Raw is true if the terminal should be set raw. + Raw bool + // TryDev indicates the TTY should try to open /dev/tty if the provided input + // is not a file descriptor. + TryDev bool + // Parent is an optional interrupt handler provided to this function - if provided + // it will be invoked after the terminal state is restored. If it is not provided, + // a signal received during the TTY will result in os.Exit(0) being invoked. + Parent *interrupt.Handler + + // sizeQueue is set after a call to MonitorSize() and is used to monitor SIGWINCH signals when the + // user's terminal resizes. + sizeQueue *sizeQueue +} + +// IsTerminalIn returns true if t.In is a terminal. Does not check /dev/tty +// even if TryDev is set. +func (t TTY) IsTerminalIn() bool { + return IsTerminal(t.In) +} + +// IsTerminalOut returns true if t.Out is a terminal. Does not check /dev/tty +// even if TryDev is set. +func (t TTY) IsTerminalOut() bool { + return IsTerminal(t.Out) +} + +// IsTerminal returns whether the passed object is a terminal or not +func IsTerminal(i interface{}) bool { + _, terminal := term.GetFdInfo(i) + return terminal +} + +// Safe invokes the provided function and will attempt to ensure that when the +// function returns (or a termination signal is sent) that the terminal state +// is reset to the condition it was in prior to the function being invoked. If +// t.Raw is true the terminal will be put into raw mode prior to calling the function. +// If the input file descriptor is not a TTY and TryDev is true, the /dev/tty file +// will be opened (if available). +func (t TTY) Safe(fn SafeFunc) error { + inFd, isTerminal := term.GetFdInfo(t.In) + + if !isTerminal && t.TryDev { + if f, err := os.Open("/dev/tty"); err == nil { + defer f.Close() + inFd = f.Fd() + isTerminal = term.IsTerminal(inFd) + } + } + if !isTerminal { + return fn() + } + + var state *term.State + var err error + if t.Raw { + state, err = term.MakeRaw(inFd) + } else { + state, err = term.SaveState(inFd) + } + if err != nil { + return err + } + return interrupt.Chain(t.Parent, func() { + if t.sizeQueue != nil { + t.sizeQueue.stop() + } + + term.RestoreTerminal(inFd, state) + }).Run(fn) +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/term/term_writer.go b/vendor/k8s.io/kubernetes/pkg/util/term/term_writer.go new file mode 100644 index 000000000..2d72d1e45 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/term/term_writer.go @@ -0,0 +1,124 @@ +/* +Copyright 2016 The Kubernetes 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 term + +import ( + "io" + "os" + + "github.com/docker/docker/pkg/term" + wordwrap "github.com/mitchellh/go-wordwrap" +) + +type wordWrapWriter struct { + limit uint + writer io.Writer +} + +// NewResponsiveWriter creates a Writer that detects the column width of the +// terminal we are in, and adjusts every line width to fit and use recommended +// terminal sizes for better readability. Does proper word wrapping automatically. +// if terminal width >= 120 columns use 120 columns +// if terminal width >= 100 columns use 100 columns +// if terminal width >= 80 columns use 80 columns +// In case we're not in a terminal or if it's smaller than 80 columns width, +// doesn't do any wrapping. +func NewResponsiveWriter(w io.Writer) io.Writer { + file, ok := w.(*os.File) + if !ok { + return w + } + fd := file.Fd() + if !term.IsTerminal(fd) { + return w + } + + terminalSize := GetSize(fd) + if terminalSize == nil { + return w + } + + var limit uint + switch { + case terminalSize.Width >= 120: + limit = 120 + case terminalSize.Width >= 100: + limit = 100 + case terminalSize.Width >= 80: + limit = 80 + } + + return NewWordWrapWriter(w, limit) +} + +// NewWordWrapWriter is a Writer that supports a limit of characters on every line +// and does auto word wrapping that respects that limit. +func NewWordWrapWriter(w io.Writer, limit uint) io.Writer { + return &wordWrapWriter{ + limit: limit, + writer: w, + } +} + +func (w wordWrapWriter) Write(p []byte) (nn int, err error) { + if w.limit == 0 { + return w.writer.Write(p) + } + original := string(p) + wrapped := wordwrap.WrapString(original, w.limit) + return w.writer.Write([]byte(wrapped)) +} + +// NewPunchCardWriter is a NewWordWrapWriter that limits the line width to 80 columns. +func NewPunchCardWriter(w io.Writer) io.Writer { + return NewWordWrapWriter(w, 80) +} + +type maxWidthWriter struct { + maxWidth uint + currentWidth uint + written uint + writer io.Writer +} + +// NewMaxWidthWriter is a Writer that supports a limit of characters on every +// line, but doesn't do any word wrapping automatically. +func NewMaxWidthWriter(w io.Writer, maxWidth uint) io.Writer { + return &maxWidthWriter{ + maxWidth: maxWidth, + writer: w, + } +} + +func (m maxWidthWriter) Write(p []byte) (nn int, err error) { + for _, b := range p { + if m.currentWidth == m.maxWidth { + m.writer.Write([]byte{'\n'}) + m.currentWidth = 0 + } + if b == '\n' { + m.currentWidth = 0 + } + _, err := m.writer.Write([]byte{b}) + if err != nil { + return int(m.written), err + } + m.written++ + m.currentWidth++ + } + return len(p), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/umask.go b/vendor/k8s.io/kubernetes/pkg/util/umask.go new file mode 100644 index 000000000..35ccce50b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/umask.go @@ -0,0 +1,27 @@ +// +build !windows + +/* +Copyright 2014 The Kubernetes 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 util + +import ( + "syscall" +) + +func Umask(mask int) (old int, err error) { + return syscall.Umask(mask), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/umask_windows.go b/vendor/k8s.io/kubernetes/pkg/util/umask_windows.go new file mode 100644 index 000000000..7a1ba1538 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/umask_windows.go @@ -0,0 +1,27 @@ +// +build windows + +/* +Copyright 2014 The Kubernetes 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 util + +import ( + "errors" +) + +func Umask(mask int) (int, error) { + return 0, errors.New("platform and architecture is not supported") +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/util.go b/vendor/k8s.io/kubernetes/pkg/util/util.go new file mode 100644 index 000000000..389e145e8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/util.go @@ -0,0 +1,140 @@ +/* +Copyright 2014 The Kubernetes 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 util + +import ( + "fmt" + "os" + "reflect" + "regexp" +) + +// Takes a list of strings and compiles them into a list of regular expressions +func CompileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) { + regexps := []*regexp.Regexp{} + for _, regexpStr := range regexpStrings { + r, err := regexp.Compile(regexpStr) + if err != nil { + return []*regexp.Regexp{}, err + } + regexps = append(regexps, r) + } + return regexps, nil +} + +// Detects if using systemd as the init system +// Please note that simply reading /proc/1/cmdline can be misleading because +// some installation of various init programs can automatically make /sbin/init +// a symlink or even a renamed version of their main program. +// TODO(dchen1107): realiably detects the init system using on the system: +// systemd, upstart, initd, etc. +func UsingSystemdInitSystem() bool { + if _, err := os.Stat("/run/systemd/system"); err == nil { + return true + } + + return false +} + +// Tests whether all pointer fields in a struct are nil. This is useful when, +// for example, an API struct is handled by plugins which need to distinguish +// "no plugin accepted this spec" from "this spec is empty". +// +// This function is only valid for structs and pointers to structs. Any other +// type will cause a panic. Passing a typed nil pointer will return true. +func AllPtrFieldsNil(obj interface{}) bool { + v := reflect.ValueOf(obj) + if !v.IsValid() { + panic(fmt.Sprintf("reflect.ValueOf() produced a non-valid Value for %#v", obj)) + } + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return true + } + v = v.Elem() + } + for i := 0; i < v.NumField(); i++ { + if v.Field(i).Kind() == reflect.Ptr && !v.Field(i).IsNil() { + return false + } + } + return true +} + +func FileExists(filename string) (bool, error) { + if _, err := os.Stat(filename); os.IsNotExist(err) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} + +func FileOrSymlinkExists(filename string) (bool, error) { + if _, err := os.Lstat(filename); os.IsNotExist(err) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} + +// ReadDirNoStat returns a string of files/directories contained +// in dirname without calling lstat on them. +func ReadDirNoStat(dirname string) ([]string, error) { + if dirname == "" { + dirname = "." + } + + f, err := os.Open(dirname) + if err != nil { + return nil, err + } + defer f.Close() + + return f.Readdirnames(-1) +} + +// IntPtr returns a pointer to an int +func IntPtr(i int) *int { + o := i + return &o +} + +// Int32Ptr returns a pointer to an int32 +func Int32Ptr(i int32) *int32 { + o := i + return &o +} + +// IntPtrDerefOr dereference the int ptr and returns it i not nil, +// else returns def. +func IntPtrDerefOr(ptr *int, def int) int { + if ptr != nil { + return *ptr + } + return def +} + +// Int32PtrDerefOr dereference the int32 ptr and returns it i not nil, +// else returns def. +func Int32PtrDerefOr(ptr *int32, def int32) int32 { + if ptr != nil { + return *ptr + } + return def +} diff --git a/vendor/k8s.io/kubernetes/pkg/util/version/doc.go b/vendor/k8s.io/kubernetes/pkg/util/version/doc.go new file mode 100644 index 000000000..ebe43152e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/version/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes 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 version provides utilities for version number comparisons +package version // import "k8s.io/kubernetes/pkg/util/version" diff --git a/vendor/k8s.io/kubernetes/pkg/util/version/version.go b/vendor/k8s.io/kubernetes/pkg/util/version/version.go new file mode 100644 index 000000000..327f2e67f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/util/version/version.go @@ -0,0 +1,236 @@ +/* +Copyright 2016 The Kubernetes 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 version + +import ( + "bytes" + "fmt" + "regexp" + "strconv" + "strings" +) + +// Version is an opqaue representation of a version number +type Version struct { + components []uint + semver bool + preRelease string + buildMetadata string +} + +var ( + // versionMatchRE splits a version string into numeric and "extra" parts + versionMatchRE = regexp.MustCompile(`^\s*v?([0-9]+(?:\.[0-9]+)*)(.*)*$`) + // extraMatchRE splits the "extra" part of versionMatchRE into semver pre-release and build metadata; it does not validate the "no leading zeroes" constraint for pre-release + extraMatchRE = regexp.MustCompile(`^(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?\s*$`) +) + +func parse(str string, semver bool) (*Version, error) { + parts := versionMatchRE.FindStringSubmatch(str) + if parts == nil { + return nil, fmt.Errorf("could not parse %q as version", str) + } + numbers, extra := parts[1], parts[2] + + components := strings.Split(numbers, ".") + if (semver && len(components) != 3) || (!semver && len(components) < 2) { + return nil, fmt.Errorf("illegal version string %q", str) + } + + v := &Version{ + components: make([]uint, len(components)), + semver: semver, + } + for i, comp := range components { + if (i == 0 || semver) && strings.HasPrefix(comp, "0") && comp != "0" { + return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) + } + num, err := strconv.ParseUint(comp, 10, 0) + if err != nil { + return nil, fmt.Errorf("illegal non-numeric version component %q in %q: %v", comp, str, err) + } + v.components[i] = uint(num) + } + + if semver && extra != "" { + extraParts := extraMatchRE.FindStringSubmatch(extra) + if extraParts == nil { + return nil, fmt.Errorf("could not parse pre-release/metadata (%s) in version %q", extra, str) + } + v.preRelease, v.buildMetadata = extraParts[1], extraParts[2] + + for _, comp := range strings.Split(v.preRelease, ".") { + if _, err := strconv.ParseUint(comp, 10, 0); err == nil { + if strings.HasPrefix(comp, "0") && comp != "0" { + return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) + } + } + } + } + + return v, nil +} + +// ParseGeneric parses a "generic" version string. The version string must consist of two +// or more dot-separated numeric fields (the first of which can't have leading zeroes), +// followed by arbitrary uninterpreted data (which need not be separated from the final +// numeric field by punctuation). For convenience, leading and trailing whitespace is +// ignored, and the version can be preceded by the letter "v". See also ParseSemantic. +func ParseGeneric(str string) (*Version, error) { + return parse(str, false) +} + +// MustParseGeneric is like ParseGeneric except that it panics on error +func MustParseGeneric(str string) *Version { + v, err := ParseGeneric(str) + if err != nil { + panic(err) + } + return v +} + +// ParseSemantic parses a version string that exactly obeys the syntax and semantics of +// the "Semantic Versioning" specification (http://semver.org/) (although it ignores +// leading and trailing whitespace, and allows the version to be preceded by "v"). For +// version strings that are not guaranteed to obey the Semantic Versioning syntax, use +// ParseGeneric. +func ParseSemantic(str string) (*Version, error) { + return parse(str, true) +} + +// MustParseSemantic is like ParseSemantic except that it panics on error +func MustParseSemantic(str string) *Version { + v, err := ParseSemantic(str) + if err != nil { + panic(err) + } + return v +} + +// BuildMetadata returns the build metadata, if v is a Semantic Version, or "" +func (v *Version) BuildMetadata() string { + return v.buildMetadata +} + +// String converts a Version back to a string; note that for versions parsed with +// ParseGeneric, this will not include the trailing uninterpreted portion of the version +// number. +func (v *Version) String() string { + var buffer bytes.Buffer + + for i, comp := range v.components { + if i > 0 { + buffer.WriteString(".") + } + buffer.WriteString(fmt.Sprintf("%d", comp)) + } + if v.preRelease != "" { + buffer.WriteString("-") + buffer.WriteString(v.preRelease) + } + if v.buildMetadata != "" { + buffer.WriteString("+") + buffer.WriteString(v.buildMetadata) + } + + return buffer.String() +} + +// compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0 +// if they are equal +func (v *Version) compareInternal(other *Version) int { + for i := range v.components { + switch { + case i >= len(other.components): + if v.components[i] != 0 { + return 1 + } + case other.components[i] < v.components[i]: + return 1 + case other.components[i] > v.components[i]: + return -1 + } + } + + if !v.semver || !other.semver { + return 0 + } + + switch { + case v.preRelease == "" && other.preRelease != "": + return 1 + case v.preRelease != "" && other.preRelease == "": + return -1 + case v.preRelease == other.preRelease: // includes case where both are "" + return 0 + } + + vPR := strings.Split(v.preRelease, ".") + oPR := strings.Split(other.preRelease, ".") + for i := range vPR { + if i >= len(oPR) { + return 1 + } + vNum, err := strconv.ParseUint(vPR[i], 10, 0) + if err == nil { + oNum, err := strconv.ParseUint(oPR[i], 10, 0) + if err == nil { + switch { + case oNum < vNum: + return 1 + case oNum > vNum: + return -1 + default: + continue + } + } + } + if oPR[i] < vPR[i] { + return 1 + } else if oPR[i] > vPR[i] { + return -1 + } + } + + return 0 +} + +// AtLeast tests if a version is at least equal to a given minimum version. If both +// Versions are Semantic Versions, this will use the Semantic Version comparison +// algorithm. Otherwise, it will compare only the numeric components, with non-present +// components being considered "0" (ie, "1.4" is equal to "1.4.0"). +func (v *Version) AtLeast(min *Version) bool { + return v.compareInternal(min) != -1 +} + +// LessThan tests if a version is less than a given version. (It is exactly the opposite +// of AtLeast, for situations where asking "is v too old?" makes more sense than asking +// "is v new enough?".) +func (v *Version) LessThan(other *Version) bool { + return v.compareInternal(other) == -1 +} + +// Compare compares v against a version string (which will be parsed as either Semantic +// or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if +// it is greater than other, or 0 if they are equal. +func (v *Version) Compare(other string) (int, error) { + ov, err := parse(other, v.semver) + if err != nil { + return 0, err + } + return v.compareInternal(ov), nil +} |