add DHCP IPAM plugin
The plugin binary actually functions in two modes. The first mode is a regular CNI plugin. The second mode (when stared with "daemon" arg) runs a DHCP client daemon. When executed as a CNI plugin, it issues an RPC request to the daemon for actual processing. The daemon is required since a DHCP lease needs to be maintained by periodically renewing it. One instance of the daemon can server arbitrary number of containers/leases.
This commit is contained in:
parent
7a8ee49891
commit
c70320b5ed
190
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir.go
generated
vendored
190
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir.go
generated
vendored
@ -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
|
|
||||||
}
|
|
156
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir_test.go
generated
vendored
156
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/dir_test.go
generated
vendored
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
272
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock.go
generated
vendored
272
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock.go
generated
vendored
@ -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
|
|
||||||
}
|
|
203
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock_test.go
generated
vendored
203
Godeps/_workspace/src/github.com/coreos/rkt/pkg/lock/keylock_test.go
generated
vendored
@ -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
|
|
||||||
}
|
|
19
pkg/ns/ns.go
19
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
|
// WithNetNSPath executes the passed closure under the given network
|
||||||
// namespace, restoring the original namespace afterwards.
|
// 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)
|
ns, err := os.Open(nspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to open %v: %v", nspath, err)
|
return fmt.Errorf("Failed to open %v: %v", nspath, err)
|
||||||
}
|
}
|
||||||
defer ns.Close()
|
defer ns.Close()
|
||||||
|
return WithNetNS(ns, lockThread, f)
|
||||||
return WithNetNS(ns, f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithNetNS executes the passed closure under the given network
|
// WithNetNS executes the passed closure under the given network
|
||||||
// namespace, restoring the original namespace afterwards.
|
// 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
|
// save a handle to current (host) network namespace
|
||||||
thisNS, err := os.Open("/proc/self/ns/net")
|
thisNS, err := os.Open("/proc/self/ns/net")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
157
plugins/ipam/dhcp/daemon.go
Normal file
157
plugins/ipam/dhcp/daemon.go
Normal file
@ -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)
|
||||||
|
}
|
329
plugins/ipam/dhcp/lease.go
Normal file
329
plugins/ipam/dhcp/lease.go
Normal file
@ -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),
|
||||||
|
)
|
||||||
|
}
|
64
plugins/ipam/dhcp/main.go
Normal file
64
plugins/ipam/dhcp/main.go
Normal file
@ -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
|
||||||
|
}
|
139
plugins/ipam/dhcp/options.go
Normal file
139
plugins/ipam/dhcp/options.go
Normal file
@ -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")
|
||||||
|
}
|
75
plugins/ipam/dhcp/options_test.go
Normal file
75
plugins/ipam/dhcp/options_test.go
Normal file
@ -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)
|
||||||
|
}
|
@ -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 {
|
func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int) error {
|
||||||
var hostVethName string
|
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
|
// create the veth pair in the container and move host end into host netns
|
||||||
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
|
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,7 +196,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)
|
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)
|
return plugin.ConfigureIface(args.IfName, result)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -235,7 +235,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
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)
|
return ip.DelLinkByName(args.IfName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ func createIpvlan(conf *NetConf, ifName string, netns *os.File) error {
|
|||||||
return fmt.Errorf("failed to create ipvlan: %v", err)
|
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)
|
err := renameLink(tmpName, ifName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to rename ipvlan to %q: %v", ifName, err)
|
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")
|
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)
|
return plugin.ConfigureIface(args.IfName, result)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -159,7 +159,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
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)
|
return ip.DelLinkByName(args.IfName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ func createMacvlan(conf *NetConf, ifName string, netns *os.File) error {
|
|||||||
return fmt.Errorf("failed to create macvlan: %v", err)
|
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)
|
err := renameLink(tmpName, ifName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to rename macvlan to %q: %v", ifName, err)
|
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")
|
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)
|
return plugin.ConfigureIface(args.IfName, result)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -163,7 +163,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
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)
|
return ip.DelLinkByName(args.IfName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ type NetConf struct {
|
|||||||
|
|
||||||
func setupContainerVeth(netns, ifName string, mtu int, pr *plugin.Result) (string, error) {
|
func setupContainerVeth(netns, ifName string, mtu int, pr *plugin.Result) (string, error) {
|
||||||
var hostVethName string
|
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)
|
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -131,7 +131,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ipn *net.IPNet
|
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
|
var err error
|
||||||
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
|
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
|
||||||
return err
|
return err
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
# Run a command in a private network namespace
|
# Run a command in a private network namespace
|
||||||
# set up by CNI plugins
|
# set up by CNI plugins
|
||||||
|
|
||||||
contid=$(printf '%x%x%x%x' $RANDOM $RANDOM $RANDOM $RANDOM)
|
contid=$(printf '%x%x%x%x' $RANDOM $RANDOM $RANDOM $RANDOM)
|
||||||
netnspath=/var/run/netns/$contid
|
netnspath=/var/run/netns/$contid
|
||||||
|
|
||||||
@ -17,4 +16,4 @@ function cleanup() {
|
|||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
ip netns exec $contid $@
|
ip netns exec $contid "$@"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user