Merge pull request #8133 from crosbymichael/update-libcontainer-sep7

Update libcontainer to 185328a42654f6dc9a41814e578
This commit is contained in:
Tibor Vass 2014-09-19 14:58:51 -04:00
commit 7fafe170cf
11 changed files with 267 additions and 148 deletions

View file

@ -62,7 +62,7 @@ if [ "$1" = '--go' ]; then
mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
fi fi
clone git github.com/docker/libcontainer 84ad9386a0240acb7475429a835d826007032bf9 clone git github.com/docker/libcontainer 185328a42654f6dc9a41814e57882f69d65f6ab7
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
rm -rf src/github.com/docker/libcontainer/vendor rm -rf src/github.com/docker/libcontainer/vendor
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')" eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"

View file

@ -24,6 +24,23 @@ var (
CgroupProcesses = "cgroup.procs" CgroupProcesses = "cgroup.procs"
) )
// The absolute path to the root of the cgroup hierarchies.
var cgroupRoot string
// TODO(vmarmol): Report error here, we'll probably need to wait for the new API.
func init() {
// we can pick any subsystem to find the root
cpuRoot, err := cgroups.FindCgroupMountpoint("cpu")
if err != nil {
return
}
cgroupRoot = filepath.Dir(cpuRoot)
if _, err := os.Stat(cgroupRoot); err != nil {
return
}
}
type subsystem interface { type subsystem interface {
// Returns the stats, as 'stats', corresponding to the cgroup under 'path'. // Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
GetStats(path string, stats *cgroups.Stats) error GetStats(path string, stats *cgroups.Stats) error
@ -121,15 +138,8 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) {
} }
func getCgroupData(c *cgroups.Cgroup, pid int) (*data, error) { func getCgroupData(c *cgroups.Cgroup, pid int) (*data, error) {
// we can pick any subsystem to find the root if cgroupRoot == "" {
cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") return nil, fmt.Errorf("failed to find the cgroup root")
if err != nil {
return nil, err
}
cgroupRoot = filepath.Dir(cgroupRoot)
if _, err := os.Stat(cgroupRoot); err != nil {
return nil, fmt.Errorf("cgroups fs not found")
} }
cgroup := c.Name cgroup := c.Name

View file

@ -14,58 +14,65 @@ type Container interface {
// Returns the current run state of the container. // Returns the current run state of the container.
// //
// Errors: container no longer exists, // Errors:
// system error. // ContainerDestroyed - Container no longer exists,
RunState() (*RunState, error) // SystemError - System error.
RunState() (*RunState, Error)
// Returns the current config of the container. // Returns the current config of the container.
Config() *Config Config() *Config
// Start a process inside the container. Returns the PID of the new process (in the caller process's namespace) and a channel that will return the exit status of the process whenever it dies. // Start a process inside the container. Returns the PID of the new process (in the caller process's namespace) and a channel that will return the exit status of the process whenever it dies.
// //
// Errors: container no longer exists, // Errors:
// config is invalid, // ContainerDestroyed - Container no longer exists,
// container is paused, // ConfigInvalid - config is invalid,
// system error. // ContainerPaused - Container is paused,
Start(*ProcessConfig) (pid int, exitChan chan int, err error) // SystemError - System error.
Start(config *ProcessConfig) (pid int, exitChan chan int, err Error)
// Destroys the container after killing all running processes. // Destroys the container after killing all running processes.
// //
// Any event registrations are removed before the container is destroyed. // Any event registrations are removed before the container is destroyed.
// No error is returned if the container is already destroyed. // No error is returned if the container is already destroyed.
// //
// Errors: system error. // Errors:
Destroy() error // SystemError - System error.
Destroy() Error
// Returns the PIDs inside this container. The PIDs are in the namespace of the calling process. // Returns the PIDs inside this container. The PIDs are in the namespace of the calling process.
// //
// Errors: container no longer exists, // Errors:
// system error. // ContainerDestroyed - Container no longer exists,
// SystemError - System error.
// //
// Some of the returned PIDs may no longer refer to processes in the Container, unless // Some of the returned PIDs may no longer refer to processes in the Container, unless
// the Container state is PAUSED in which case every PID in the slice is valid. // the Container state is PAUSED in which case every PID in the slice is valid.
Processes() ([]int, error) Processes() ([]int, Error)
// Returns statistics for the container. // Returns statistics for the container.
// //
// Errors: container no longer exists, // Errors:
// system error. // ContainerDestroyed - Container no longer exists,
Stats() (*ContainerStats, error) // SystemError - System error.
Stats() (*ContainerStats, Error)
// If the Container state is RUNNING or PAUSING, sets the Container state to PAUSING and pauses // If the Container state is RUNNING or PAUSING, sets the Container state to PAUSING and pauses
// the execution of any user processes. Asynchronously, when the container finished being paused the // the execution of any user processes. Asynchronously, when the container finished being paused the
// state is changed to PAUSED. // state is changed to PAUSED.
// If the Container state is PAUSED, do nothing. // If the Container state is PAUSED, do nothing.
// //
// Errors: container no longer exists, // Errors:
// system error. // ContainerDestroyed - Container no longer exists,
Pause() error // SystemError - System error.
Pause() Error
// If the Container state is PAUSED, resumes the execution of any user processes in the // If the Container state is PAUSED, resumes the execution of any user processes in the
// Container before setting the Container state to RUNNING. // Container before setting the Container state to RUNNING.
// If the Container state is RUNNING, do nothing. // If the Container state is RUNNING, do nothing.
// //
// Errors: container no longer exists, // Errors:
// system error. // ContainerDestroyed - Container no longer exists,
Resume() error // SystemError - System error.
Resume() Error
} }

View file

@ -17,6 +17,12 @@ var (
ErrNotADeviceNode = errors.New("not a device node") ErrNotADeviceNode = errors.New("not a device node")
) )
// Testing dependencies
var (
osLstat = os.Lstat
ioutilReadDir = ioutil.ReadDir
)
type Device struct { type Device struct {
Type rune `json:"type,omitempty"` Type rune `json:"type,omitempty"`
Path string `json:"path,omitempty"` // It is fine if this is an empty string in the case that you are using Wildcards Path string `json:"path,omitempty"` // It is fine if this is an empty string in the case that you are using Wildcards
@ -42,7 +48,7 @@ func (device *Device) GetCgroupAllowString() string {
// Given the path to a device and it's cgroup_permissions(which cannot be easilly queried) look up the information about a linux device and return that information as a Device struct. // Given the path to a device and it's cgroup_permissions(which cannot be easilly queried) look up the information about a linux device and return that information as a Device struct.
func GetDevice(path, cgroupPermissions string) (*Device, error) { func GetDevice(path, cgroupPermissions string) (*Device, error) {
fileInfo, err := os.Lstat(path) fileInfo, err := osLstat(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -87,7 +93,7 @@ func GetHostDeviceNodes() ([]*Device, error) {
} }
func getDeviceNodes(path string) ([]*Device, error) { func getDeviceNodes(path string) ([]*Device, error) {
files, err := ioutil.ReadDir(path) files, err := ioutilReadDir(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -0,0 +1,61 @@
package devices
import (
"errors"
"os"
"testing"
)
func TestGetDeviceLstatFailure(t *testing.T) {
testError := errors.New("test error")
// Override os.Lstat to inject error.
osLstat = func(path string) (os.FileInfo, error) {
return nil, testError
}
_, err := GetDevice("", "")
if err != testError {
t.Fatalf("Unexpected error %v, expected %v", err, testError)
}
}
func TestGetHostDeviceNodesIoutilReadDirFailure(t *testing.T) {
testError := errors.New("test error")
// Override ioutil.ReadDir to inject error.
ioutilReadDir = func(dirname string) ([]os.FileInfo, error) {
return nil, testError
}
_, err := GetHostDeviceNodes()
if err != testError {
t.Fatalf("Unexpected error %v, expected %v", err, testError)
}
}
func TestGetHostDeviceNodesIoutilReadDirDeepFailure(t *testing.T) {
testError := errors.New("test error")
called := false
// Override ioutil.ReadDir to inject error after the first call.
ioutilReadDir = func(dirname string) ([]os.FileInfo, error) {
if called {
return nil, testError
}
called = true
// Provoke a second call.
fi, err := os.Lstat("/tmp")
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
return []os.FileInfo{fi}, nil
}
_, err := GetHostDeviceNodes()
if err != testError {
t.Fatalf("Unexpected error %v, expected %v", err, testError)
}
}

View file

@ -0,0 +1,37 @@
package libcontainer
// API error code type.
type ErrorCode int
// API error codes.
const (
// Factory errors
IdInUse ErrorCode = iota
InvalidIdFormat
// TODO: add Load errors
// Container errors
ContainerDestroyed
ContainerPaused
// Common errors
ConfigInvalid
SystemError
)
// API Error type.
type Error interface {
error
// Returns the stack trace, if any, which identifies the
// point at which the error occurred.
Stack() []byte
// Returns a verbose string including the error message
// and a representation of the stack trace suitable for
// printing.
Detail() string
// Returns the error code for this error.
Code() ErrorCode
}

View file

@ -12,13 +12,13 @@ type Factory interface {
// Returns the new container with a running process. // Returns the new container with a running process.
// //
// Errors: // Errors:
// id is already in use by a container // IdInUse - id is already in use by a container
// id has incorrect format // InvalidIdFormat - id has incorrect format
// config is invalid // ConfigInvalid - config is invalid
// System error // SystemError - System error
// //
// On error, any partially created container parts are cleaned up (the operation is atomic). // On error, any partially created container parts are cleaned up (the operation is atomic).
Create(id string, config *Config) (Container, error) Create(id string, config *Config) (Container, Error)
// Load takes an ID for an existing container and reconstructs the container // Load takes an ID for an existing container and reconstructs the container
// from the state. // from the state.
@ -27,5 +27,6 @@ type Factory interface {
// Path does not exist // Path does not exist
// Container is stopped // Container is stopped
// System error // System error
Load(id string) (Container, error) // TODO: fix description
Load(id string) (Container, Error)
} }

View file

@ -3,6 +3,8 @@ package netlink
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
"math/rand"
"net" "net"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
@ -38,12 +40,15 @@ type ifreqFlags struct {
Ifruflags uint16 Ifruflags uint16
} }
func nativeEndian() binary.ByteOrder { var native binary.ByteOrder
func init() {
var x uint32 = 0x01020304 var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 { if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian native = binary.BigEndian
} else {
native = binary.LittleEndian
} }
return binary.LittleEndian
} }
func getIpFamily(ip net.IP) int { func getIpFamily(ip net.IP) int {
@ -80,8 +85,6 @@ func newIfInfomsgChild(parent *RtAttr, family int) *IfInfomsg {
} }
func (msg *IfInfomsg) ToWireFormat() []byte { func (msg *IfInfomsg) ToWireFormat() []byte {
native := nativeEndian()
length := syscall.SizeofIfInfomsg length := syscall.SizeofIfInfomsg
b := make([]byte, length) b := make([]byte, length)
b[0] = msg.Family b[0] = msg.Family
@ -110,8 +113,6 @@ func newIfAddrmsg(family int) *IfAddrmsg {
} }
func (msg *IfAddrmsg) ToWireFormat() []byte { func (msg *IfAddrmsg) ToWireFormat() []byte {
native := nativeEndian()
length := syscall.SizeofIfAddrmsg length := syscall.SizeofIfAddrmsg
b := make([]byte, length) b := make([]byte, length)
b[0] = msg.Family b[0] = msg.Family
@ -142,8 +143,6 @@ func newRtMsg() *RtMsg {
} }
func (msg *RtMsg) ToWireFormat() []byte { func (msg *RtMsg) ToWireFormat() []byte {
native := nativeEndian()
length := syscall.SizeofRtMsg length := syscall.SizeofRtMsg
b := make([]byte, length) b := make([]byte, length)
b[0] = msg.Family b[0] = msg.Family
@ -202,8 +201,6 @@ func (a *RtAttr) Len() int {
} }
func (a *RtAttr) ToWireFormat() []byte { func (a *RtAttr) ToWireFormat() []byte {
native := nativeEndian()
length := a.Len() length := a.Len()
buf := make([]byte, rtaAlignOf(length)) buf := make([]byte, rtaAlignOf(length))
@ -225,14 +222,18 @@ func (a *RtAttr) ToWireFormat() []byte {
return buf return buf
} }
func uint32Attr(t int, n uint32) *RtAttr {
buf := make([]byte, 4)
native.PutUint32(buf, n)
return newRtAttr(t, buf)
}
type NetlinkRequest struct { type NetlinkRequest struct {
syscall.NlMsghdr syscall.NlMsghdr
Data []NetlinkRequestData Data []NetlinkRequestData
} }
func (rr *NetlinkRequest) ToWireFormat() []byte { func (rr *NetlinkRequest) ToWireFormat() []byte {
native := nativeEndian()
length := rr.Len length := rr.Len
dataBytes := make([][]byte, len(rr.Data)) dataBytes := make([][]byte, len(rr.Data))
for i, data := range rr.Data { for i, data := range rr.Data {
@ -329,36 +330,44 @@ func (s *NetlinkSocket) GetPid() (uint32, error) {
return 0, ErrWrongSockType return 0, ErrWrongSockType
} }
func (s *NetlinkSocket) HandleAck(seq uint32) error { func (s *NetlinkSocket) CheckMessage(m syscall.NetlinkMessage, seq, pid uint32) error {
native := nativeEndian() if m.Header.Seq != seq {
return fmt.Errorf("netlink: invalid seq %d, expected %d", m.Header.Seq, seq)
}
if m.Header.Pid != pid {
return fmt.Errorf("netlink: wrong pid %d, expected %d", m.Header.Pid, pid)
}
if m.Header.Type == syscall.NLMSG_DONE {
return io.EOF
}
if m.Header.Type == syscall.NLMSG_ERROR {
e := int32(native.Uint32(m.Data[0:4]))
if e == 0 {
return io.EOF
}
return syscall.Errno(-e)
}
return nil
}
func (s *NetlinkSocket) HandleAck(seq uint32) error {
pid, err := s.GetPid() pid, err := s.GetPid()
if err != nil { if err != nil {
return err return err
} }
done: outer:
for { for {
msgs, err := s.Receive() msgs, err := s.Receive()
if err != nil { if err != nil {
return err return err
} }
for _, m := range msgs { for _, m := range msgs {
if m.Header.Seq != seq { if err := s.CheckMessage(m, seq, pid); err != nil {
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, seq) if err == io.EOF {
} break outer
if m.Header.Pid != pid {
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
}
if m.Header.Type == syscall.NLMSG_DONE {
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
error := int32(native.Uint32(m.Data[0:4]))
if error == 0 {
break done
} }
return syscall.Errno(-error) return err
} }
} }
} }
@ -454,17 +463,11 @@ func AddRoute(destination, source, gateway, device string) error {
wb.AddData(attr) wb.AddData(attr)
} }
var (
native = nativeEndian()
b = make([]byte, 4)
)
iface, err := net.InterfaceByName(device) iface, err := net.InterfaceByName(device)
if err != nil { if err != nil {
return err return err
} }
native.PutUint32(b, uint32(iface.Index)) wb.AddData(uint32Attr(syscall.RTA_OIF, uint32(iface.Index)))
wb.AddData(newRtAttr(syscall.RTA_OIF, b))
if err := s.Send(wb); err != nil { if err := s.Send(wb); err != nil {
return err return err
@ -538,15 +541,7 @@ func NetworkSetMTU(iface *net.Interface, mtu int) error {
msg.Index = int32(iface.Index) msg.Index = int32(iface.Index)
msg.Change = DEFAULT_CHANGE msg.Change = DEFAULT_CHANGE
wb.AddData(msg) wb.AddData(msg)
wb.AddData(uint32Attr(syscall.IFLA_MTU, uint32(mtu)))
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(mtu))
data := newRtAttr(syscall.IFLA_MTU, b)
wb.AddData(data)
if err := s.Send(wb); err != nil { if err := s.Send(wb); err != nil {
return err return err
@ -570,15 +565,7 @@ func NetworkSetMaster(iface, master *net.Interface) error {
msg.Index = int32(iface.Index) msg.Index = int32(iface.Index)
msg.Change = DEFAULT_CHANGE msg.Change = DEFAULT_CHANGE
wb.AddData(msg) wb.AddData(msg)
wb.AddData(uint32Attr(syscall.IFLA_MASTER, uint32(master.Index)))
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(master.Index))
data := newRtAttr(syscall.IFLA_MASTER, b)
wb.AddData(data)
if err := s.Send(wb); err != nil { if err := s.Send(wb); err != nil {
return err return err
@ -602,15 +589,7 @@ func NetworkSetNsPid(iface *net.Interface, nspid int) error {
msg.Index = int32(iface.Index) msg.Index = int32(iface.Index)
msg.Change = DEFAULT_CHANGE msg.Change = DEFAULT_CHANGE
wb.AddData(msg) wb.AddData(msg)
wb.AddData(uint32Attr(syscall.IFLA_NET_NS_PID, uint32(nspid)))
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(nspid))
data := newRtAttr(syscall.IFLA_NET_NS_PID, b)
wb.AddData(data)
if err := s.Send(wb); err != nil { if err := s.Send(wb); err != nil {
return err return err
@ -634,15 +613,7 @@ func NetworkSetNsFd(iface *net.Interface, fd int) error {
msg.Index = int32(iface.Index) msg.Index = int32(iface.Index)
msg.Change = DEFAULT_CHANGE msg.Change = DEFAULT_CHANGE
wb.AddData(msg) wb.AddData(msg)
wb.AddData(uint32Attr(IFLA_NET_NS_FD, uint32(fd)))
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(fd))
data := newRtAttr(IFLA_NET_NS_FD, b)
wb.AddData(data)
if err := s.Send(wb); err != nil { if err := s.Send(wb); err != nil {
return err return err
@ -782,8 +753,6 @@ func NetworkLinkDel(name string) error {
// Returns an array of IPNet for all the currently routed subnets on ipv4 // Returns an array of IPNet for all the currently routed subnets on ipv4
// This is similar to the first column of "ip route" output // This is similar to the first column of "ip route" output
func NetworkGetRoutes() ([]Route, error) { func NetworkGetRoutes() ([]Route, error) {
native := nativeEndian()
s, err := getNetlinkSocket() s, err := getNetlinkSocket()
if err != nil { if err != nil {
return nil, err return nil, err
@ -806,28 +775,18 @@ func NetworkGetRoutes() ([]Route, error) {
res := make([]Route, 0) res := make([]Route, 0)
done: outer:
for { for {
msgs, err := s.Receive() msgs, err := s.Receive()
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, m := range msgs { for _, m := range msgs {
if m.Header.Seq != wb.Seq { if err := s.CheckMessage(m, wb.Seq, pid); err != nil {
return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq) if err == io.EOF {
} break outer
if m.Header.Pid != pid {
return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
}
if m.Header.Type == syscall.NLMSG_DONE {
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
error := int32(native.Uint32(m.Data[0:4]))
if error == 0 {
break done
} }
return nil, syscall.Errno(-error) return nil, err
} }
if m.Header.Type != syscall.RTM_NEWROUTE { if m.Header.Type != syscall.RTM_NEWROUTE {
continue continue
@ -974,7 +933,7 @@ func CreateBridge(name string, setMacAddr bool) error {
return err return err
} }
if setMacAddr { if setMacAddr {
return setBridgeMacAddress(s, name) return NetworkSetMacAddress(name, randMacAddr())
} }
return nil return nil
} }
@ -1030,22 +989,40 @@ func AddToBridge(iface, master *net.Interface) error {
return nil return nil
} }
func setBridgeMacAddress(s int, name string) error { func randMacAddr() string {
hw := make(net.HardwareAddr, 6)
for i := 0; i < 6; i++ {
hw[i] = byte(rand.Intn(255))
}
hw[0] &^= 0x1 // clear multicast bit
hw[0] |= 0x2 // set local assignment bit (IEEE802)
return hw.String()
}
func NetworkSetMacAddress(name, addr string) error {
if len(name) >= IFNAMSIZ { if len(name) >= IFNAMSIZ {
return fmt.Errorf("Interface name %s too long", name) return fmt.Errorf("Interface name %s too long", name)
} }
hw, err := net.ParseMAC(addr)
if err != nil {
return err
}
s, err := getIfSocket()
if err != nil {
return err
}
defer syscall.Close(s)
ifr := ifreqHwaddr{} ifr := ifreqHwaddr{}
ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER
copy(ifr.IfrnName[:len(ifr.IfrnName)-1], name) copy(ifr.IfrnName[:len(ifr.IfrnName)-1], name)
for i := 0; i < 6; i++ { for i := 0; i < 6; i++ {
ifr.IfruHwaddr.Data[i] = randIfrDataByte() ifr.IfruHwaddr.Data[i] = ifrDataByte(hw[i])
} }
ifr.IfruHwaddr.Data[0] &^= 0x1 // clear multicast bit
ifr.IfruHwaddr.Data[0] |= 0x2 // set local assignment bit (IEEE802)
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 { if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 {
return err return err
} }

View file

@ -1,9 +1,5 @@
package netlink package netlink
import ( func ifrDataByte(b byte) uint8 {
"math/rand" return uint8(b)
)
func randIfrDataByte() uint8 {
return uint8(rand.Intn(255))
} }

View file

@ -2,10 +2,6 @@
package netlink package netlink
import ( func ifrDataByte(b byte) int8 {
"math/rand" return int8(b)
)
func randIfrDataByte() int8 {
return int8(rand.Intn(255))
} }

View file

@ -115,6 +115,7 @@ func TestCreateVethPair(t *testing.T) {
if err := NetworkCreateVethPair(name1, name2); err != nil { if err := NetworkCreateVethPair(name1, name2); err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer NetworkLinkDel(name1)
if _, err := net.InterfaceByName(name1); err != nil { if _, err := net.InterfaceByName(name1); err != nil {
t.Fatal(err) t.Fatal(err)
@ -124,3 +125,30 @@ func TestCreateVethPair(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestSetMACAddress(t *testing.T) {
if testing.Short() {
return
}
name := "testmac"
mac := randMacAddr()
if err := NetworkLinkAdd(name, "bridge"); err != nil {
t.Fatal(err)
}
defer NetworkLinkDel(name)
if err := NetworkSetMacAddress(name, mac); err != nil {
t.Fatal(err)
}
iface, err := net.InterfaceByName(name)
if err != nil {
t.Fatal(err)
}
if iface.HardwareAddr.String() != mac {
t.Fatalf("mac address %q does not match %q", iface.HardwareAddr, mac)
}
}