diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir.go b/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir.go deleted file mode 100644 index 265a2769..00000000 --- a/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir.go +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2014 CoreOS, Inc. -// -// 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 lock implements simple locking primitives on a -// regular file or directory using flock -package lock - -import ( - "errors" - "syscall" -) - -var ( - ErrLocked = errors.New("file already locked") - ErrNotExist = errors.New("file does not exist") - ErrPermission = errors.New("permission denied") - ErrNotRegular = errors.New("not a regular file") -) - -// FileLock represents a lock on a regular file or a directory -type FileLock struct { - path string - fd int -} - -type LockType int - -const ( - Dir LockType = iota - RegFile -) - -// TryExclusiveLock takes an exclusive lock without blocking. -// This is idempotent when the Lock already represents an exclusive lock, -// and tries promote a shared lock to exclusive atomically. -// It will return ErrLocked if any lock is already held. -func (l *FileLock) TryExclusiveLock() error { - err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB) - if err == syscall.EWOULDBLOCK { - err = ErrLocked - } - return err -} - -// TryExclusiveLock takes an exclusive lock on a file/directory without blocking. -// It will return ErrLocked if any lock is already held on the file/directory. -func TryExclusiveLock(path string, lockType LockType) (*FileLock, error) { - l, err := NewLock(path, lockType) - if err != nil { - return nil, err - } - err = l.TryExclusiveLock() - if err != nil { - return nil, err - } - return l, err -} - -// ExclusiveLock takes an exclusive lock. -// This is idempotent when the Lock already represents an exclusive lock, -// and promotes a shared lock to exclusive atomically. -// It will block if an exclusive lock is already held. -func (l *FileLock) ExclusiveLock() error { - return syscall.Flock(l.fd, syscall.LOCK_EX) -} - -// ExclusiveLock takes an exclusive lock on a file/directory. -// It will block if an exclusive lock is already held on the file/directory. -func ExclusiveLock(path string, lockType LockType) (*FileLock, error) { - l, err := NewLock(path, lockType) - if err == nil { - err = l.ExclusiveLock() - } - if err != nil { - return nil, err - } - return l, nil -} - -// TrySharedLock takes a co-operative (shared) lock without blocking. -// This is idempotent when the Lock already represents a shared lock, -// and tries demote an exclusive lock to shared atomically. -// It will return ErrLocked if an exclusive lock already exists. -func (l *FileLock) TrySharedLock() error { - err := syscall.Flock(l.fd, syscall.LOCK_SH|syscall.LOCK_NB) - if err == syscall.EWOULDBLOCK { - err = ErrLocked - } - return err -} - -// TrySharedLock takes a co-operative (shared) lock on a file/directory without blocking. -// It will return ErrLocked if an exclusive lock already exists on the file/directory. -func TrySharedLock(path string, lockType LockType) (*FileLock, error) { - l, err := NewLock(path, lockType) - if err != nil { - return nil, err - } - err = l.TrySharedLock() - if err != nil { - return nil, err - } - return l, nil -} - -// SharedLock takes a co-operative (shared) lock on. -// This is idempotent when the Lock already represents a shared lock, -// and demotes an exclusive lock to shared atomically. -// It will block if an exclusive lock is already held. -func (l *FileLock) SharedLock() error { - return syscall.Flock(l.fd, syscall.LOCK_SH) -} - -// SharedLock takes a co-operative (shared) lock on a file/directory. -// It will block if an exclusive lock is already held on the file/directory. -func SharedLock(path string, lockType LockType) (*FileLock, error) { - l, err := NewLock(path, lockType) - if err != nil { - return nil, err - } - err = l.SharedLock() - if err != nil { - return nil, err - } - return l, nil -} - -// Unlock unlocks the lock -func (l *FileLock) Unlock() error { - return syscall.Flock(l.fd, syscall.LOCK_UN) -} - -// Fd returns the lock's file descriptor, or an error if the lock is closed -func (l *FileLock) Fd() (int, error) { - var err error - if l.fd == -1 { - err = errors.New("lock closed") - } - return l.fd, err -} - -// Close closes the lock which implicitly unlocks it as well -func (l *FileLock) Close() error { - fd := l.fd - l.fd = -1 - return syscall.Close(fd) -} - -// NewLock opens a new lock on a file without acquisition -func NewLock(path string, lockType LockType) (*FileLock, error) { - l := &FileLock{path: path, fd: -1} - - mode := syscall.O_RDONLY | syscall.O_CLOEXEC - if lockType == Dir { - mode |= syscall.O_DIRECTORY - } - lfd, err := syscall.Open(l.path, mode, 0) - if err != nil { - if err == syscall.ENOENT { - err = ErrNotExist - } else if err == syscall.EACCES { - err = ErrPermission - } - return nil, err - } - l.fd = lfd - - var stat syscall.Stat_t - err = syscall.Fstat(lfd, &stat) - if err != nil { - return nil, err - } - // Check if the file is a regular file - if lockType == RegFile && !(stat.Mode&syscall.S_IFMT == syscall.S_IFREG) { - return nil, ErrNotRegular - } - - return l, nil -} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir_test.go b/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir_test.go deleted file mode 100644 index fb866264..00000000 --- a/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir_test.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2014 CoreOS, Inc. -// -// 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 lock - -import ( - "io/ioutil" - "os" - "testing" -) - -func TestNewLock(t *testing.T) { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("error creating tmpfile: %v", err) - } - defer os.Remove(f.Name()) - f.Close() - - l, err := NewLock(f.Name(), RegFile) - if err != nil { - t.Fatalf("error creating NewFileLock: %v", err) - } - l.Close() - - d, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("error creating tmpdir: %v", err) - } - defer os.Remove(d) - - l, err = NewLock(d, Dir) - if err != nil { - t.Fatalf("error creating NewLock: %v", err) - } - - err = l.Close() - if err != nil { - t.Fatalf("error unlocking lock: %v", err) - } - - if err = os.Remove(d); err != nil { - t.Fatalf("error removing tmpdir: %v", err) - } - - l, err = NewLock(d, Dir) - if err == nil { - t.Fatalf("expected error creating lock on nonexistent path") - } -} - -func TestExclusiveLock(t *testing.T) { - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("error creating tmpdir: %v", err) - } - defer os.Remove(dir) - - // Set up the initial exclusive lock - l, err := ExclusiveLock(dir, Dir) - if err != nil { - t.Fatalf("error creating lock: %v", err) - } - - // reacquire the exclusive lock using the receiver interface - err = l.TryExclusiveLock() - if err != nil { - t.Fatalf("error reacquiring exclusive lock: %v", err) - } - - // Now try another exclusive lock, should fail - _, err = TryExclusiveLock(dir, Dir) - if err == nil { - t.Fatalf("expected err trying exclusive lock") - } - - // Unlock the original lock - err = l.Close() - if err != nil { - t.Fatalf("error closing lock: %v", err) - } - - // Now another exclusive lock should succeed - _, err = TryExclusiveLock(dir, Dir) - if err != nil { - t.Fatalf("error creating lock: %v", err) - } -} - -func TestSharedLock(t *testing.T) { - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("error creating tmpdir: %v", err) - } - defer os.Remove(dir) - - // Set up the initial shared lock - l1, err := SharedLock(dir, Dir) - if err != nil { - t.Fatalf("error creating new shared lock: %v", err) - } - - err = l1.TrySharedLock() - if err != nil { - t.Fatalf("error reacquiring shared lock: %v", err) - } - - // Subsequent shared locks should succeed - l2, err := TrySharedLock(dir, Dir) - if err != nil { - t.Fatalf("error creating shared lock: %v", err) - } - l3, err := TrySharedLock(dir, Dir) - if err != nil { - t.Fatalf("error creating shared lock: %v", err) - } - - // But an exclusive lock should fail - _, err = TryExclusiveLock(dir, Dir) - if err == nil { - t.Fatal("expected exclusive lock to fail") - } - - // Close the locks - err = l1.Close() - if err != nil { - t.Fatalf("error closing lock: %v", err) - } - err = l2.Close() - if err != nil { - t.Fatalf("error closing lock: %v", err) - } - - // Only unlock one of them - err = l3.Unlock() - if err != nil { - t.Fatalf("error unlocking lock: %v", err) - } - - // Now try an exclusive lock, should succeed - _, err = TryExclusiveLock(dir, Dir) - if err != nil { - t.Fatalf("error creating lock: %v", err) - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock.go b/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock.go deleted file mode 100644 index 768b4216..00000000 --- a/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// 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 lock - -import ( - "fmt" - "os" - "path/filepath" - "syscall" -) - -const ( - defaultDirPerm os.FileMode = 0660 - defaultFilePerm os.FileMode = 0660 - defaultLockRetries = 3 -) - -type keyLockMode uint - -const ( - keyLockExclusive keyLockMode = 1 << iota - keyLockShared - keyLockNonBlocking -) - -// KeyLock is a lock for a specific key. The lock file is created inside a -// directory using the key name. -// This is useful when multiple processes want to take a lock but cannot use -// FileLock as they don't have a well defined file on the filesystem. -// key value must be a valid file name (as the lock file is named after the key -// value). -type KeyLock struct { - lockDir string - key string - // The lock on the key - keyLock *FileLock -} - -// NewKeyLock returns a KeyLock for the specified key without acquisition. -// lockdir is the directory where the lock file will be created. If lockdir -// doesn't exists it will be created. -// key value must be a valid file name (as the lock file is named after the key -// value). -func NewKeyLock(lockDir string, key string) (*KeyLock, error) { - err := os.MkdirAll(lockDir, defaultDirPerm) - if err != nil { - return nil, err - } - keyLockFile := filepath.Join(lockDir, key) - // create the file if it doesn't exists - f, err := os.OpenFile(keyLockFile, os.O_RDONLY|os.O_CREATE, defaultFilePerm) - if err != nil { - return nil, fmt.Errorf("error creating key lock file: %v", err) - } - f.Close() - keyLock, err := NewLock(keyLockFile, RegFile) - if err != nil { - return nil, fmt.Errorf("error opening key lock file: %v", err) - } - return &KeyLock{lockDir: lockDir, key: key, keyLock: keyLock}, nil -} - -// Close closes the key lock which implicitly unlocks it as well -func (l *KeyLock) Close() { - l.keyLock.Close() -} - -// TryExclusiveLock takes an exclusive lock on a key without blocking. -// This is idempotent when the KeyLock already represents an exclusive lock, -// and tries promote a shared lock to exclusive atomically. -// It will return ErrLocked if any lock is already held on the key. -func (l *KeyLock) TryExclusiveKeyLock() error { - return l.lock(keyLockExclusive|keyLockNonBlocking, defaultLockRetries) -} - -// TryExclusiveLock takes an exclusive lock on the key without blocking. -// lockDir is the directory where the lock file will be created. -// It will return ErrLocked if any lock is already held. -func TryExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) { - return createAndLock(lockDir, key, keyLockExclusive|keyLockNonBlocking) -} - -// ExclusiveLock takes an exclusive lock on a key. -// This is idempotent when the KeyLock already represents an exclusive lock, -// and promotes a shared lock to exclusive atomically. -// It will block if an exclusive lock is already held on the key. -func (l *KeyLock) ExclusiveKeyLock() error { - return l.lock(keyLockExclusive, defaultLockRetries) -} - -// ExclusiveLock takes an exclusive lock on a key. -// lockDir is the directory where the lock file will be created. -// It will block if an exclusive lock is already held on the key. -func ExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) { - return createAndLock(lockDir, key, keyLockExclusive) -} - -// TrySharedLock takes a co-operative (shared) lock on the key without blocking. -// This is idempotent when the KeyLock already represents a shared lock, -// and tries demote an exclusive lock to shared atomically. -// It will return ErrLocked if an exclusive lock already exists on the key. -func (l *KeyLock) TrySharedKeyLock() error { - return l.lock(keyLockShared|keyLockNonBlocking, defaultLockRetries) -} - -// TrySharedLock takes a co-operative (shared) lock on a key without blocking. -// lockDir is the directory where the lock file will be created. -// It will return ErrLocked if an exclusive lock already exists on the key. -func TrySharedKeyLock(lockDir string, key string) (*KeyLock, error) { - return createAndLock(lockDir, key, keyLockShared|keyLockNonBlocking) -} - -// SharedLock takes a co-operative (shared) lock on a key. -// This is idempotent when the KeyLock already represents a shared lock, -// and demotes an exclusive lock to shared atomically. -// It will block if an exclusive lock is already held on the key. -func (l *KeyLock) SharedKeyLock() error { - return l.lock(keyLockShared, defaultLockRetries) -} - -// SharedLock takes a co-operative (shared) lock on a key. -// lockDir is the directory where the lock file will be created. -// It will block if an exclusive lock is already held on the key. -func SharedKeyLock(lockDir string, key string) (*KeyLock, error) { - return createAndLock(lockDir, key, keyLockShared) -} - -func createAndLock(lockDir string, key string, mode keyLockMode) (*KeyLock, error) { - keyLock, err := NewKeyLock(lockDir, key) - if err != nil { - return nil, err - } - err = keyLock.lock(mode, defaultLockRetries) - if err != nil { - return nil, err - } - return keyLock, nil -} - -// lock is the base function to take a lock and handle changed lock files -// As there's the need to remove unused (see CleanKeyLocks) lock files without -// races, a changed file detection is needed. -// -// Without changed file detection this can happen: -// -// Process A takes exclusive lock on file01 -// Process B waits for exclusive lock on file01. -// Process A deletes file01 and then releases the lock. -// Process B takes the lock on the removed file01 as it has the fd opened -// Process C comes, creates the file as it doesn't exists, and it also takes an exclusive lock. -// Now B and C thinks to own an exclusive lock. -// -// maxRetries can be passed, useful for testing. -func (l *KeyLock) lock(mode keyLockMode, maxRetries int) error { - retries := 0 - for { - var err error - var isExclusive bool - var isNonBlocking bool - if mode&keyLockExclusive != 0 { - isExclusive = true - } - if mode&keyLockNonBlocking != 0 { - isNonBlocking = true - } - switch { - case isExclusive && !isNonBlocking: - err = l.keyLock.ExclusiveLock() - case isExclusive && isNonBlocking: - err = l.keyLock.TryExclusiveLock() - case !isExclusive && !isNonBlocking: - err = l.keyLock.SharedLock() - case !isExclusive && isNonBlocking: - err = l.keyLock.TrySharedLock() - } - if err != nil { - return err - } - - // Check that the file referenced by the lock fd is the same as - // the current file on the filesystem - var lockStat, curStat syscall.Stat_t - lfd, err := l.keyLock.Fd() - if err != nil { - return err - } - err = syscall.Fstat(lfd, &lockStat) - if err != nil { - return err - } - keyLockFile := filepath.Join(l.lockDir, l.key) - fd, err := syscall.Open(keyLockFile, syscall.O_RDONLY, 0) - // If there's an error opening the file return an error - if err != nil { - return err - } - err = syscall.Fstat(fd, &curStat) - if err != nil { - return err - } - if lockStat.Ino == curStat.Ino && lockStat.Dev == curStat.Dev { - return nil - } - if retries >= maxRetries { - return fmt.Errorf("cannot acquire lock after %d retries", retries) - } - - // If the file has changed discard this lock and try to take another lock. - l.keyLock.Close() - nl, err := NewKeyLock(l.lockDir, l.key) - if err != nil { - return err - } - l.keyLock = nl.keyLock - - retries++ - } -} - -// Unlock unlocks the key lock. -func (l *KeyLock) Unlock() error { - err := l.keyLock.Unlock() - if err != nil { - return err - } - return nil -} - -// CleanKeyLocks remove lock files from the lockDir. -// For every key it tries to take an Exclusive lock on it and skip it if it -// fails with ErrLocked -func CleanKeyLocks(lockDir string) error { - f, err := os.Open(lockDir) - if err != nil { - return fmt.Errorf("error opening lockDir: %v", err) - } - defer f.Close() - files, err := f.Readdir(0) - if err != nil { - return fmt.Errorf("error getting lock files list: %v", err) - } - for _, f := range files { - filename := filepath.Join(lockDir, f.Name()) - keyLock, err := TryExclusiveKeyLock(lockDir, f.Name()) - if err == ErrLocked { - continue - } - if err != nil { - return err - } - - err = os.Remove(filename) - if err != nil { - keyLock.Close() - return fmt.Errorf("error removing lock file: %v", err) - } - keyLock.Close() - } - return nil -} diff --git a/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock_test.go b/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock_test.go deleted file mode 100644 index 56cc9f1f..00000000 --- a/Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock_test.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// 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 lock - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" -) - -func TestExclusiveKeyLock(t *testing.T) { - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("error creating tmpdir: %v", err) - } - defer os.RemoveAll(dir) - - l1, err := ExclusiveKeyLock(dir, "key01") - if err != nil { - t.Fatalf("error creating key lock: %v", err) - } - - _, err = TryExclusiveKeyLock(dir, "key01") - if err == nil { - t.Fatalf("expected err trying exclusive key lock") - } - - l1.Close() -} - -func TestCleanKeyLocks(t *testing.T) { - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("error creating tmpdir: %v", err) - } - defer os.RemoveAll(dir) - - l1, err := ExclusiveKeyLock(dir, "key01") - if err != nil { - t.Fatalf("error creating keyLock: %v", err) - } - - err = CleanKeyLocks(dir) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - filesnum, err := countFiles(dir) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if filesnum != 1 { - t.Fatalf("expected 1 file in lock dir. found %d files", filesnum) - } - - l2, err := SharedKeyLock(dir, "key02") - if err != nil { - t.Fatalf("error creating keyLock: %v", err) - } - - l1.Close() - l2.Close() - - err = CleanKeyLocks(dir) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - filesnum, err = countFiles(dir) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if filesnum != 0 { - t.Fatalf("expected empty lock dir. found %d files", filesnum) - } -} - -func TestFileChangedLock(t *testing.T) { - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("error creating tmpdir: %v", err) - } - defer os.RemoveAll(dir) - - l1, err := ExclusiveKeyLock(dir, "key01") - if err != nil { - t.Fatalf("error creating keyLock: %v", err) - } - - l2, err := NewKeyLock(dir, "key01") - if err != nil { - t.Fatalf("error creating keyLock: %v", err) - } - - // Simulate that l1 owner removes the actual key1 lock file - err = os.Remove(filepath.Join(dir, "key01")) - if err != nil { - t.Fatalf("error creating NewLock: %v", err) - } - l1.Close() - - // Now l2 owner takes a lock, using the fd of the removed file - err = l2.lock(keyLockShared, 0) - if err == nil { - t.Fatalf("expected error") - } - l2.Close() - - // Do the same with a new file created after removal - dir, err = ioutil.TempDir("", "") - if err != nil { - t.Fatalf("error creating tmpdir: %v", err) - } - defer os.RemoveAll(dir) - - l1, err = ExclusiveKeyLock(dir, "key01") - if err != nil { - t.Fatalf("error creating keyLock: %v", err) - } - - l2, err = NewKeyLock(dir, "key01") - if err != nil { - t.Fatalf("error creating keyLock: %v", err) - } - - // Simulate that l1 owner removes the actual key1 lock file - err = os.Remove(filepath.Join(dir, "key01")) - if err != nil { - t.Fatalf("error creating NewLock: %v", err) - } - l1.Close() - - // Simulate that another user comes and takes a lock, this will create - // a new lock file as it was removed. - l3, err := ExclusiveKeyLock(dir, "key01") - l3.Close() - - // Now l2 owner takes a lock, using the fd of the old file - err = l2.lock(keyLockShared, 0) - if err == nil { - t.Fatalf("expected error") - } - - // Do the same but with a retry so if should work. - dir, err = ioutil.TempDir("", "") - if err != nil { - t.Fatalf("error creating tmpdir: %v", err) - } - defer os.RemoveAll(dir) - - l1, err = ExclusiveKeyLock(dir, "key01") - if err != nil { - t.Fatalf("error creating keyLock: %v", err) - } - - l2, err = NewKeyLock(dir, "key01") - if err != nil { - t.Fatalf("error creating keyLock: %v", err) - } - - // Simulate that l1 owner removes the actual key1 lock file - err = os.Remove(filepath.Join(dir, "key01")) - if err != nil { - t.Fatalf("error creating NewLock: %v", err) - } - l1.Close() - - // Simulate that another user comes and takes a lock, this will create - // a new lock file as it was removed. - l3, err = ExclusiveKeyLock(dir, "key01") - l3.Close() - - // Now l2 owner takes a lock, using the fd of the old file - err = l2.lock(keyLockShared, 1) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func countFiles(dir string) (int, error) { - f, err := os.Open(dir) - if err != nil { - return -1, err - } - defer f.Close() - files, err := f.Readdir(0) - if err != nil { - return -1, err - } - return len(files), nil -} diff --git a/pkg/ns/ns.go b/pkg/ns/ns.go index 82291f98..20548b9b 100644 --- a/pkg/ns/ns.go +++ b/pkg/ns/ns.go @@ -48,19 +48,30 @@ func SetNS(f *os.File, flags uintptr) error { // WithNetNSPath executes the passed closure under the given network // namespace, restoring the original namespace afterwards. -func WithNetNSPath(nspath string, f func(*os.File) error) error { +// Changing namespaces must be done on a goroutine that has been +// locked to an OS thread. If lockThread arg is true, this function +// locks the goroutine prior to change namespace and unlocks before +// returning +func WithNetNSPath(nspath string, lockThread bool, f func(*os.File) error) error { ns, err := os.Open(nspath) if err != nil { return fmt.Errorf("Failed to open %v: %v", nspath, err) } defer ns.Close() - - return WithNetNS(ns, f) + return WithNetNS(ns, lockThread, f) } // WithNetNS executes the passed closure under the given network // namespace, restoring the original namespace afterwards. -func WithNetNS(ns *os.File, f func(*os.File) error) error { +// Changing namespaces must be done on a goroutine that has been +// locked to an OS thread. If lockThread arg is true, this function +// locks the goroutine prior to change namespace and unlocks before +// returning +func WithNetNS(ns *os.File, lockThread bool, f func(*os.File) error) error { + if lockThread { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + } // save a handle to current (host) network namespace thisNS, err := os.Open("/proc/self/ns/net") if err != nil { diff --git a/plugins/ipam/dhcp/daemon.go b/plugins/ipam/dhcp/daemon.go new file mode 100644 index 00000000..f39c58ee --- /dev/null +++ b/plugins/ipam/dhcp/daemon.go @@ -0,0 +1,157 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 main + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net" + "net/http" + "net/rpc" + "os" + "path/filepath" + "runtime" + "sync" + + "github.com/appc/cni/Godeps/_workspace/src/github.com/coreos/go-systemd/activation" + "github.com/appc/cni/pkg/plugin" + "github.com/appc/cni/pkg/skel" +) + +const listenFdsStart = 3 +const resendCount = 3 + +var errNoMoreTries = errors.New("no more tries") + +type DHCP struct { + mux sync.Mutex + leases map[string]*DHCPLease +} + +func newDHCP() *DHCP { + return &DHCP{ + leases: make(map[string]*DHCPLease), + } +} + +// Allocate acquires an IP from a DHCP server for a specified container. +// The acquired lease will be maintained until Release() is called. +func (d *DHCP) Allocate(args *skel.CmdArgs, result *plugin.Result) error { + conf := plugin.NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("error parsing netconf: %v", err) + } + + clientID := args.ContainerID + "/" + conf.Name + l, err := AcquireLease(clientID, args.Netns, args.IfName) + if err != nil { + return err + } + + ipn, err := l.IPNet() + if err != nil { + l.Stop() + return err + } + + d.setLease(args.ContainerID, conf.Name, l) + + result.IP4 = &plugin.IPConfig{ + IP: *ipn, + Gateway: l.Gateway(), + Routes: l.Routes(), + } + + return nil +} + +// Release stops maintenance of the lease acquired in Allocate() +// and sends a release msg to the DHCP server. +func (d *DHCP) Release(args *skel.CmdArgs, reply *struct{}) error { + conf := plugin.NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("error parsing netconf: %v", err) + } + + if l := d.getLease(args.ContainerID, conf.Name); l != nil { + l.Stop() + return nil + } + + return fmt.Errorf("lease not found: %v/%v", args.ContainerID, conf.Name) +} + +func (d *DHCP) getLease(contID, netName string) *DHCPLease { + d.mux.Lock() + defer d.mux.Unlock() + + // TODO(eyakubovich): hash it to avoid collisions + l, ok := d.leases[contID+netName] + if !ok { + return nil + } + return l +} + +func (d *DHCP) setLease(contID, netName string, l *DHCPLease) { + d.mux.Lock() + defer d.mux.Unlock() + + // TODO(eyakubovich): hash it to avoid collisions + d.leases[contID+netName] = l +} + +func getListener() (net.Listener, error) { + l, err := activation.Listeners(true) + if err != nil { + return nil, err + } + + switch { + case len(l) == 0: + if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil { + return nil, err + } + return net.Listen("unix", socketPath) + + case len(l) == 1: + if l[0] == nil { + return nil, fmt.Errorf("LISTEN_FDS=1 but no FD found") + } + return l[0], nil + + default: + return nil, fmt.Errorf("Too many (%v) FDs passed through socket activation", len(l)) + } +} + +func runDaemon() { + // since other goroutines (on separate threads) will change namespaces, + // ensure the RPC server does not get scheduled onto those + runtime.LockOSThread() + + l, err := getListener() + if err != nil { + log.Printf("Error getting listener: %v", err) + return + } + + dhcp := newDHCP() + rpc.Register(dhcp) + rpc.HandleHTTP() + http.Serve(l, nil) +} diff --git a/plugins/ipam/dhcp/lease.go b/plugins/ipam/dhcp/lease.go new file mode 100644 index 00000000..7a6377e5 --- /dev/null +++ b/plugins/ipam/dhcp/lease.go @@ -0,0 +1,329 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 main + +import ( + "fmt" + "log" + "math/rand" + "net" + "os" + "sync" + "time" + + "github.com/appc/cni/Godeps/_workspace/src/github.com/d2g/dhcp4" + "github.com/appc/cni/Godeps/_workspace/src/github.com/d2g/dhcp4client" + "github.com/appc/cni/Godeps/_workspace/src/github.com/vishvananda/netlink" + + "github.com/appc/cni/pkg/ns" + "github.com/appc/cni/pkg/plugin" +) + +// RFC 2131 suggests using exponential backoff, starting with 4sec +// and randomized to +/- 1sec +const resendDelay0 = 4 * time.Second + +const ( + leaseStateBound = iota + leaseStateRenewing + leaseStateRebinding +) + +// This implementation uses 1 OS thread per lease. This is because +// all the network operations have to be done in network namespace +// of the interface. This can be improved by switching to the proper +// namespace for network ops and using fewer threads. However, this +// needs to be done carefully as dhcp4client ops are blocking. + +type DHCPLease struct { + clientID string + ack *dhcp4.Packet + opts dhcp4.Options + link netlink.Link + renewalTime time.Time + rebindingTime time.Time + expireTime time.Time + stop chan struct{} + wg sync.WaitGroup +} + +// AcquireLease gets an DHCP lease and then maintains it in the background +// by periodically renewing it. The acquired lease can be released by +// calling DHCPLease.Stop() +func AcquireLease(clientID, netns, ifName string) (*DHCPLease, error) { + errCh := make(chan error, 1) + l := &DHCPLease{ + clientID: clientID, + stop: make(chan struct{}), + } + + log.Printf("%v: acquiring lease", clientID) + + l.wg.Add(1) + go ns.WithNetNSPath(netns, true, func(_ *os.File) (e error) { + defer l.wg.Done() + + link, err := netlink.LinkByName(ifName) + if err != nil { + errCh <- fmt.Errorf("error looking up %q", ifName) + return + } + + l.link = link + + if err = l.acquire(); err != nil { + errCh <- err + return + } + + log.Printf("%v: lease acquired, expiration is %v", l.clientID, l.expireTime) + + errCh <- nil + + l.maintain() + return + }) + + err := <-errCh + if err != nil { + return nil, err + } + + return l, nil +} + +// Stop terminates the background task that maintains the lease +// and issues a DHCP Release +func (l *DHCPLease) Stop() { + close(l.stop) + l.wg.Wait() +} + +func (l *DHCPLease) acquire() error { + c, err := newDHCPClient(l.link) + if err != nil { + return err + } + defer c.Close() + + pkt, err := backoffRetry(func() (*dhcp4.Packet, error) { + ok, ack, err := c.Request() + switch { + case err != nil: + return nil, err + case !ok: + return nil, fmt.Errorf("DHCP server NACK'd own offer") + default: + return &ack, nil + } + }) + if err != nil { + return err + } + + return l.commit(pkt) +} + +func (l *DHCPLease) commit(ack *dhcp4.Packet) error { + opts := ack.ParseOptions() + + leaseTime, err := parseLeaseTime(opts) + if err != nil { + return err + } + + rebindingTime, err := parseRebindingTime(opts) + if err != nil || rebindingTime > leaseTime { + // Per RFC 2131 Section 4.4.5, it should default to 85% of lease time + rebindingTime = leaseTime * 85 / 100 + } + + renewalTime, err := parseRenewalTime(opts) + if err != nil || renewalTime > rebindingTime { + // Per RFC 2131 Section 4.4.5, it should default to 50% of lease time + renewalTime = leaseTime / 2 + } + + now := time.Now() + l.expireTime = now.Add(leaseTime) + l.renewalTime = now.Add(renewalTime) + l.rebindingTime = now.Add(rebindingTime) + l.ack = ack + l.opts = opts + + return nil +} + +func (l *DHCPLease) maintain() { + state := leaseStateBound + + for { + var sleepDur time.Duration + + switch state { + case leaseStateBound: + sleepDur = l.renewalTime.Sub(time.Now()) + if sleepDur <= 0 { + log.Printf("%v: renewing lease", l.clientID) + state = leaseStateRenewing + continue + } + + case leaseStateRenewing: + if err := l.renew(); err != nil { + log.Printf("%v: %v", l.clientID, err) + + if time.Now().After(l.rebindingTime) { + log.Printf("%v: renawal time expired, rebinding", l.clientID) + state = leaseStateRebinding + } + } else { + log.Printf("%v: lease renewed, expiration is %v", l.clientID, l.expireTime) + state = leaseStateBound + } + + case leaseStateRebinding: + if err := l.acquire(); err != nil { + log.Printf("%v: %v", l.clientID, err) + + if time.Now().After(l.expireTime) { + log.Printf("%v: lease expired, bringing interface DOWN", l.clientID) + l.downIface() + return + } + } else { + log.Printf("%v: lease rebound, expiration is %v", l.clientID, l.expireTime) + state = leaseStateBound + } + } + + select { + case <-time.After(sleepDur): + + case <-l.stop: + if err := l.release(); err != nil { + log.Printf("%v: failed to release DHCP lease: %v", l.clientID, err) + } + return + } + } +} + +func (l *DHCPLease) downIface() { + if err := netlink.LinkSetDown(l.link); err != nil { + log.Printf("%v: failed to bring %v interface DOWN: %v", l.clientID, l.link.Attrs().Name, err) + } +} + +func (l *DHCPLease) renew() error { + c, err := newDHCPClient(l.link) + if err != nil { + return err + } + defer c.Close() + + pkt, err := backoffRetry(func() (*dhcp4.Packet, error) { + ok, ack, err := c.Renew(*l.ack) + switch { + case err != nil: + return nil, err + case !ok: + return nil, fmt.Errorf("DHCP server did not renew lease") + default: + return &ack, nil + } + }) + if err != nil { + return err + } + + l.commit(pkt) + return nil +} + +func (l *DHCPLease) release() error { + log.Printf("%v: releasing lease", l.clientID) + + c, err := newDHCPClient(l.link) + if err != nil { + return err + } + defer c.Close() + + if err = c.Release(*l.ack); err != nil { + return fmt.Errorf("failed to send DHCPRELEASE") + } + + return nil +} + +func (l *DHCPLease) IPNet() (*net.IPNet, error) { + mask := parseSubnetMask(l.opts) + if mask == nil { + return nil, fmt.Errorf("DHCP option Subnet Mask not found in DHCPACK") + } + + return &net.IPNet{ + IP: l.ack.YIAddr(), + Mask: mask, + }, nil +} + +func (l *DHCPLease) Gateway() net.IP { + return parseRouter(l.opts) +} + +func (l *DHCPLease) Routes() []plugin.Route { + routes := parseRoutes(l.opts) + return append(routes, parseCIDRRoutes(l.opts)...) +} + +// jitter returns a random value within [-span, span) range +func jitter(span time.Duration) time.Duration { + return time.Duration(float64(span) * (2.0*rand.Float64() - 1.0)) +} + +func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) { + var baseDelay time.Duration = resendDelay0 + + for i := 0; i < resendCount; i++ { + pkt, err := f() + if err == nil { + return pkt, nil + } + + log.Print(err) + + time.Sleep(baseDelay + jitter(time.Second)) + + baseDelay *= 2 + } + + return nil, errNoMoreTries +} + +func newDHCPClient(link netlink.Link) (*dhcp4client.Client, error) { + pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index) + if err != nil { + return nil, err + } + + return dhcp4client.New( + dhcp4client.HardwareAddr(link.Attrs().HardwareAddr), + dhcp4client.Timeout(10*time.Second), + dhcp4client.Broadcast(false), + dhcp4client.Connection(pktsock), + ) +} diff --git a/plugins/ipam/dhcp/main.go b/plugins/ipam/dhcp/main.go new file mode 100644 index 00000000..ccfb1982 --- /dev/null +++ b/plugins/ipam/dhcp/main.go @@ -0,0 +1,64 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 main + +import ( + "fmt" + "net/rpc" + "os" + + "github.com/appc/cni/pkg/plugin" + "github.com/appc/cni/pkg/skel" +) + +const socketPath = "/run/cni/dhcp.sock" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "daemon" { + runDaemon() + } else { + skel.PluginMain(cmdAdd, cmdDel) + } +} + +func cmdAdd(args *skel.CmdArgs) error { + client, err := rpc.DialHTTP("unix", socketPath) + if err != nil { + return fmt.Errorf("error dialing DHCP daemon: %v", err) + } + + result := &plugin.Result{} + err = client.Call("DHCP.Allocate", args, result) + if err != nil { + return fmt.Errorf("error calling DHCP.Add: %v", err) + } + + return plugin.PrintResult(result) +} + +func cmdDel(args *skel.CmdArgs) error { + client, err := rpc.DialHTTP("unix", socketPath) + if err != nil { + return fmt.Errorf("error dialing DHCP daemon: %v", err) + } + + dummy := struct{}{} + err = client.Call("DHCP.Release", args, &dummy) + if err != nil { + return fmt.Errorf("error calling DHCP.Del: %v", err) + } + + return nil +} diff --git a/plugins/ipam/dhcp/options.go b/plugins/ipam/dhcp/options.go new file mode 100644 index 00000000..1064daa6 --- /dev/null +++ b/plugins/ipam/dhcp/options.go @@ -0,0 +1,139 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 main + +import ( + "encoding/binary" + "fmt" + "net" + "time" + + "github.com/appc/cni/Godeps/_workspace/src/github.com/d2g/dhcp4" + "github.com/appc/cni/pkg/plugin" +) + +func parseRouter(opts dhcp4.Options) net.IP { + if opts, ok := opts[dhcp4.OptionRouter]; ok { + if len(opts) == 4 { + return net.IP(opts) + } + } + return nil +} + +func classfulSubnet(sn net.IP) net.IPNet { + return net.IPNet{ + IP: sn, + Mask: sn.DefaultMask(), + } +} + +func parseRoutes(opts dhcp4.Options) []plugin.Route { + // StaticRoutes format: pairs of: + // Dest = 4 bytes; Classful IP subnet + // Router = 4 bytes; IP address of router + + routes := []plugin.Route{} + if opt, ok := opts[dhcp4.OptionStaticRoute]; ok { + for len(opt) >= 8 { + sn := opt[0:4] + r := opt[4:8] + rt := plugin.Route{ + Dst: classfulSubnet(sn), + GW: r, + } + routes = append(routes, rt) + opt = opt[8:] + } + } + + return routes +} + +func parseCIDRRoutes(opts dhcp4.Options) []plugin.Route { + // See RFC4332 for format (http://tools.ietf.org/html/rfc3442) + + routes := []plugin.Route{} + if opt, ok := opts[dhcp4.OptionClasslessRouteFormat]; ok { + for len(opt) >= 5 { + width := int(opt[0]) + if width > 32 { + // error: can't have more than /32 + return nil + } + // network bits are compacted to avoid zeros + octets := 0 + if width > 0 { + octets = (width-1)/8 + 1 + } + + if len(opt) < 1+octets+4 { + // error: too short + return nil + } + + sn := make([]byte, 4) + copy(sn, opt[1:octets+1]) + + gw := net.IP(opt[octets+1 : octets+5]) + + rt := plugin.Route{ + Dst: net.IPNet{ + IP: net.IP(sn), + Mask: net.CIDRMask(width, 32), + }, + GW: gw, + } + routes = append(routes, rt) + + opt = opt[octets+5 : len(opt)] + } + } + return routes +} + +func parseSubnetMask(opts dhcp4.Options) net.IPMask { + mask, ok := opts[dhcp4.OptionSubnetMask] + if !ok { + return nil + } + + return net.IPMask(mask) +} + +func parseDuration(opts dhcp4.Options, code dhcp4.OptionCode, optName string) (time.Duration, error) { + val, ok := opts[code] + if !ok { + return 0, fmt.Errorf("option %v not found", optName) + } + if len(val) != 4 { + return 0, fmt.Errorf("option %v is not 4 bytes", optName) + } + + secs := binary.BigEndian.Uint32(val) + return time.Duration(secs) * time.Second, nil +} + +func parseLeaseTime(opts dhcp4.Options) (time.Duration, error) { + return parseDuration(opts, dhcp4.OptionIPAddressLeaseTime, "LeaseTime") +} + +func parseRenewalTime(opts dhcp4.Options) (time.Duration, error) { + return parseDuration(opts, dhcp4.OptionRenewalTimeValue, "RenewalTime") +} + +func parseRebindingTime(opts dhcp4.Options) (time.Duration, error) { + return parseDuration(opts, dhcp4.OptionRebindingTimeValue, "RebindingTime") +} diff --git a/plugins/ipam/dhcp/options_test.go b/plugins/ipam/dhcp/options_test.go new file mode 100644 index 00000000..3b4f8013 --- /dev/null +++ b/plugins/ipam/dhcp/options_test.go @@ -0,0 +1,75 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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 main + +import ( + "net" + "testing" + + "github.com/appc/cni/Godeps/_workspace/src/github.com/d2g/dhcp4" + "github.com/appc/cni/pkg/plugin" +) + +func validateRoutes(t *testing.T, routes []plugin.Route) { + expected := []plugin.Route{ + plugin.Route{ + Dst: net.IPNet{ + IP: net.IPv4(10, 0, 0, 0), + Mask: net.CIDRMask(8, 32), + }, + GW: net.IPv4(10, 1, 2, 3), + }, + plugin.Route{ + Dst: net.IPNet{ + IP: net.IPv4(192, 168, 1, 0), + Mask: net.CIDRMask(24, 32), + }, + GW: net.IPv4(192, 168, 2, 3), + }, + } + + if len(routes) != len(expected) { + t.Fatalf("wrong length slice; expected %v, got %v", len(expected), len(routes)) + } + + for i := 0; i < len(routes); i++ { + a := routes[i] + e := expected[i] + + if a.Dst.String() != e.Dst.String() { + t.Errorf("route.Dst mismatch: expected %v, got %v", e.Dst, a.Dst) + } + + if !a.GW.Equal(e.GW) { + t.Errorf("route.GW mismatch: expected %v, got %v", e.GW, a.GW) + } + } +} + +func TestParseRoutes(t *testing.T) { + opts := make(dhcp4.Options) + opts[dhcp4.OptionStaticRoute] = []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3} + routes := parseRoutes(opts) + + validateRoutes(t, routes) +} + +func TestParseCIDRRoutes(t *testing.T) { + opts := make(dhcp4.Options) + opts[dhcp4.OptionClasslessRouteFormat] = []byte{8, 10, 10, 1, 2, 3, 24, 192, 168, 1, 192, 168, 2, 3} + routes := parseCIDRRoutes(opts) + + validateRoutes(t, routes) +} diff --git a/plugins/main/bridge/bridge.go b/plugins/main/bridge/bridge.go index 2515c0e6..c30f2caa 100644 --- a/plugins/main/bridge/bridge.go +++ b/plugins/main/bridge/bridge.go @@ -124,7 +124,7 @@ func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) { func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int) error { var hostVethName string - err := ns.WithNetNSPath(netns, func(hostNS *os.File) error { + err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error { // create the veth pair in the container and move host end into host netns hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS) if err != nil { @@ -196,7 +196,7 @@ func cmdAdd(args *skel.CmdArgs) error { result.IP4.Gateway = calcGatewayIP(&result.IP4.IP) } - err = ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error { + err = ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error { return plugin.ConfigureIface(args.IfName, result) }) if err != nil { @@ -235,7 +235,7 @@ func cmdDel(args *skel.CmdArgs) error { return err } - return ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error { + return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error { return ip.DelLinkByName(args.IfName) }) } diff --git a/plugins/main/ipvlan/ipvlan.go b/plugins/main/ipvlan/ipvlan.go index 0d2ba7a6..0f3656c3 100644 --- a/plugins/main/ipvlan/ipvlan.go +++ b/plugins/main/ipvlan/ipvlan.go @@ -97,7 +97,7 @@ func createIpvlan(conf *NetConf, ifName string, netns *os.File) error { return fmt.Errorf("failed to create ipvlan: %v", err) } - return ns.WithNetNS(netns, func(_ *os.File) error { + return ns.WithNetNS(netns, false, func(_ *os.File) error { err := renameLink(tmpName, ifName) if err != nil { return fmt.Errorf("failed to rename ipvlan to %q: %v", ifName, err) @@ -131,7 +131,7 @@ func cmdAdd(args *skel.CmdArgs) error { return errors.New("IPAM plugin returned missing IPv4 config") } - err = ns.WithNetNS(netns, func(_ *os.File) error { + err = ns.WithNetNS(netns, false, func(_ *os.File) error { return plugin.ConfigureIface(args.IfName, result) }) if err != nil { @@ -159,7 +159,7 @@ func cmdDel(args *skel.CmdArgs) error { return err } - return ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error { + return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error { return ip.DelLinkByName(args.IfName) }) } diff --git a/plugins/main/macvlan/macvlan.go b/plugins/main/macvlan/macvlan.go index 31a17eac..f4b432d8 100644 --- a/plugins/main/macvlan/macvlan.go +++ b/plugins/main/macvlan/macvlan.go @@ -101,7 +101,7 @@ func createMacvlan(conf *NetConf, ifName string, netns *os.File) error { return fmt.Errorf("failed to create macvlan: %v", err) } - return ns.WithNetNS(netns, func(_ *os.File) error { + return ns.WithNetNS(netns, false, func(_ *os.File) error { err := renameLink(tmpName, ifName) if err != nil { return fmt.Errorf("failed to rename macvlan to %q: %v", ifName, err) @@ -135,7 +135,7 @@ func cmdAdd(args *skel.CmdArgs) error { return errors.New("IPAM plugin returned missing IPv4 config") } - err = ns.WithNetNS(netns, func(_ *os.File) error { + err = ns.WithNetNS(netns, false, func(_ *os.File) error { return plugin.ConfigureIface(args.IfName, result) }) if err != nil { @@ -163,7 +163,7 @@ func cmdDel(args *skel.CmdArgs) error { return err } - return ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error { + return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error { return ip.DelLinkByName(args.IfName) }) } diff --git a/plugins/main/veth/veth.go b/plugins/main/veth/veth.go index aef901b8..c7360603 100644 --- a/plugins/main/veth/veth.go +++ b/plugins/main/veth/veth.go @@ -46,7 +46,7 @@ type NetConf struct { func setupContainerVeth(netns, ifName string, mtu int, pr *plugin.Result) (string, error) { var hostVethName string - err := ns.WithNetNSPath(netns, func(hostNS *os.File) error { + err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error { hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS) if err != nil { return err @@ -131,7 +131,7 @@ func cmdDel(args *skel.CmdArgs) error { } var ipn *net.IPNet - err := ns.WithNetNSPath(args.Netns, func(hostNS *os.File) error { + err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error { var err error ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4) return err diff --git a/scripts/priv-net-run.sh b/scripts/priv-net-run.sh index 3d1c1ecc..d96d31e5 100755 --- a/scripts/priv-net-run.sh +++ b/scripts/priv-net-run.sh @@ -2,7 +2,6 @@ # Run a command in a private network namespace # set up by CNI plugins - contid=$(printf '%x%x%x%x' $RANDOM $RANDOM $RANDOM $RANDOM) netnspath=/var/run/netns/$contid @@ -17,4 +16,4 @@ function cleanup() { } trap cleanup EXIT -ip netns exec $contid $@ +ip netns exec $contid "$@"