<commit_before>// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package ipcache

import (
	"errors"
	"fmt"
	"net"
	"sync"
	"unsafe"

	"golang.org/x/sys/unix"

	"github.com/cilium/cilium/pkg/bpf"
	"github.com/cilium/cilium/pkg/cidr"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/types"
)

var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "map-ipcache")

const (
	// MaxEntries is the maximum number of keys that can be present in the
	// RemoteEndpointMap.
	MaxEntries = 512000

	// Name is the canonical name for the IPCache map on the filesystem.
	Name = "cilium_ipcache"

	// maxPrefixLengths is an approximation of how many different CIDR
	// prefix lengths may be supported by the BPF datapath without causing
	// BPF code generation to exceed the verifier instruction limit.
	// It applies to Linux versions that lack support for LPM, ie < v4.11.
	//
	// This is based upon the defines in bpf/ep_config.h, which in turn
	// are derived by building the bpf/ directory and running the script
	// test/bpf/verifier-test.sh, then adjusting the number of unique
	// prefix lengths until the script passes.
	maxPrefixLengths6 = 4
	maxPrefixLengths4 = 18
)

// Key implements the bpf.MapKey interface.
//
// Must be in sync with struct ipcache_key in <bpf/lib/maps.h>
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=github.com/cilium/cilium/pkg/bpf.MapKey
type Key struct {
	Prefixlen uint32 `align:"lpm_key"`
	Pad1      uint16 `align:"pad1"`
	Pad2      uint8  `align:"pad2"`
	Family    uint8  `align:"family"`
	// represents both IPv6 and IPv4 (in the lowest four bytes)
	IP types.IPv6 `align:"$union0"`
}

// GetKeyPtr returns the unsafe pointer to the BPF key
func (k *Key) GetKeyPtr() unsafe.Pointer { return unsafe.Pointer(k) }

// NewValue returns a new empty instance of the structure representing the BPF
// map value
func (k Key) NewValue() bpf.MapValue { return &RemoteEndpointInfo{} }

func getStaticPrefixBits() uint32 {
	staticMatchSize := unsafe.Sizeof(Key{})
	staticMatchSize -= unsafe.Sizeof(Key{}.Prefixlen)
	staticMatchSize -= unsafe.Sizeof(Key{}.IP)
	return uint32(staticMatchSize) * 8
}

func (k Key) String() string {
	prefixLen := k.Prefixlen - getStaticPrefixBits()
	switch k.Family {
	case bpf.EndpointKeyIPv4:
		ipStr := net.IP(k.IP[:net.IPv4len]).String()
		return fmt.Sprintf("%s/%d", ipStr, prefixLen)
	case bpf.EndpointKeyIPv6:
		ipStr := k.IP.String()
		return fmt.Sprintf("%s/%d", ipStr, prefixLen)
	}
	return "<unknown>"
}

// getPrefixLen determines the length that should be set inside the Key so that
// the lookup prefix is correct in the BPF map key. The specified 'prefixBits'
// indicates the number of bits in the IP that must match to match the entry in
// the BPF ipcache.
func getPrefixLen(prefixBits int) uint32 {
	return getStaticPrefixBits() + uint32(prefixBits)
}

// NewKey returns an Key based on the provided IP address and mask. The address
// family is automatically detected
func NewKey(ip net.IP, mask net.IPMask) Key {
	result := Key{}

	ones, _ := mask.Size()
	if ip4 := ip.To4(); ip4 != nil {
		if mask == nil {
			ones = net.IPv4len * 8
		}
		result.Prefixlen = getPrefixLen(ones)
		result.Family = bpf.EndpointKeyIPv4
		copy(result.IP[:], ip4)
	} else {
		if mask == nil {
			ones = net.IPv6len * 8
		}
		result.Prefixlen = getPrefixLen(ones)
		result.Family = bpf.EndpointKeyIPv6
		copy(result.IP[:], ip)
	}

	return result
}

// RemoteEndpointInfo implements the bpf.MapValue interface. It contains the
// security identity of a remote endpoint.
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=github.com/cilium/cilium/pkg/bpf.MapValue
type RemoteEndpointInfo struct {
	SecurityIdentity uint32     `align:"sec_label"`
	TunnelEndpoint   types.IPv4 `align:"tunnel_endpoint"`
	Key              uint8      `align:"key"`
}

func (v *RemoteEndpointInfo) String() string {
	return fmt.Sprintf("identity=%d encryptkey=%d tunnelendpoint=%s",
		v.SecurityIdentity, v.Key, v.TunnelEndpoint)
}

// GetValuePtr returns the unsafe pointer to the BPF value.
func (v *RemoteEndpointInfo) GetValuePtr() unsafe.Pointer { return unsafe.Pointer(v) }

// Map represents an IPCache BPF map.
type Map struct {
	bpf.Map

	// detectDeleteSupport is used to initialize 'supportsDelete' the first
	// time that a delete is issued from the datapath.
	detectDeleteSupport sync.Once

	// deleteSupport is set to 'true' initially, then is updated to set
	// whether the underlying kernel supports delete operations on the map
	// the first time that supportsDelete() is called.
	deleteSupport bool
}

// NewMap instantiates a Map.
func NewMap(name string) *Map {
	return &Map{
		Map: *bpf.NewMap(
			name,
			bpf.MapTypeLPMTrie,
			&Key{},
			int(unsafe.Sizeof(Key{})),
			&RemoteEndpointInfo{},
			int(unsafe.Sizeof(RemoteEndpointInfo{})),
			MaxEntries,
			bpf.BPF_F_NO_PREALLOC, 0,
			bpf.ConvertKeyValue,
		).WithCache().WithPressureMetric(),
		deleteSupport: true,
	}
}

// delete removes a key from the ipcache BPF map, and returns whether the
// kernel supports the delete operation (true) or not (false), and any error
// that may have occurred while attempting to delete the entry.
//
// If "overwrite" is true, then if delete is not supported the entry's value
// will be overwritten with zeroes to signify that it's an invalid entry.
func (m *Map) delete(k bpf.MapKey, overwrite bool) (bool, error) {
	err := m.Delete(k)
	var errno unix.Errno
	if ok := errors.As(err, &errno); ok && errno == unix.ENOSYS {
		if overwrite {
			// Older kernels do not support deletion of LPM map entries so zero out
			// the entry instead of attempting a deletion
			return false, m.Update(k, &RemoteEndpointInfo{})
		}
		return false, nil
	}
	return true, err
}

// DeleteWithOverwrite removes a key from the ipcache BPF map.
// If delete is not supported, the entry's value will be overwritten with
// zeroes to signify that it's an invalid entry.
func (m *Map) DeleteWithOverwrite(k bpf.MapKey) error {
	_, err := m.delete(k, true)
	return err
}

// GetMaxPrefixLengths determines how many unique prefix lengths are supported
// simultaneously based on the underlying BPF map type in use.
func (m *Map) GetMaxPrefixLengths() (ipv6, ipv4 int) {
	if BackedByLPM() {
		return net.IPv6len*8 + 1, net.IPv4len*8 + 1
	}
	return maxPrefixLengths6, maxPrefixLengths4
}

func (m *Map) supportsDelete() bool {
	m.detectDeleteSupport.Do(func() {
		// Entry is invalid because IPCache needs a family specified.
		invalidEntry := &Key{}
		m.deleteSupport, _ = m.delete(invalidEntry, false)
		log.Debugf("Detected IPCache delete operation support: %t", m.deleteSupport)

		// In addition to delete support, ability to dump the map is
		// also required in order to run the garbage collector which
		// will iterate over the map and delete entries.
		if m.deleteSupport {
			err := m.Dump(map[string][]string{})
			m.deleteSupport = err == nil
			log.Debugf("Detected IPCache dump operation support: %t", m.deleteSupport)
		}

		if !m.deleteSupport {
			log.Infof("Periodic IPCache map swap will occur due to lack of kernel support for LPM delete operation. Upgrade to Linux 4.15 or higher to avoid this.")
		}
	})
	return m.deleteSupport
}

// SupportsDelete determines whether the underlying kernel map type supports
// the delete operation.
func SupportsDelete() bool {
	return IPCache.supportsDelete()
}

// BackedByLPM returns true if the IPCache is backed by a proper LPM
// implementation (provided by Linux kernels 4.11 or later), false otherwise.
func BackedByLPM() bool {
	return IPCache.MapType == bpf.MapTypeLPMTrie
}

var (
	// IPCache is a mapping of all endpoint IPs in the cluster which this
	// Cilium agent is a part of to their corresponding security identities.
	// It is a singleton; there is only one such map per agent.
	IPCache = NewMap(Name)
)

// Reopen attempts to close and re-open the IPCache map at the standard path
// on the filesystem.
func Reopen() error {
	return IPCache.Map.Reopen()
}

// Function to update IPCache map with VTEP CIDR
func UpdateIPCacheVTEPMapping(newCIDR *cidr.CIDR, newTunnelEndpoint net.IP,
	securityIdentity uint32, encryptKey uint8) error {

	key := NewKey(newCIDR.IP, newCIDR.Mask)

	value := RemoteEndpointInfo{
		SecurityIdentity: securityIdentity,
		Key:              encryptKey,
	}
	if ip4 := newTunnelEndpoint.To4(); ip4 != nil {
		copy(value.TunnelEndpoint[:], ip4)
	}

	return IPCache.Update(&key, &value)

}
<commit_msg>ipcache: Add SupportsDump() helper<commit_after>// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package ipcache

import (
	"errors"
	"fmt"
	"net"
	"sync"
	"unsafe"

	"golang.org/x/sys/unix"

	"github.com/cilium/cilium/pkg/bpf"
	"github.com/cilium/cilium/pkg/cidr"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/types"
)

var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "map-ipcache")

const (
	// MaxEntries is the maximum number of keys that can be present in the
	// RemoteEndpointMap.
	MaxEntries = 512000

	// Name is the canonical name for the IPCache map on the filesystem.
	Name = "cilium_ipcache"

	// maxPrefixLengths is an approximation of how many different CIDR
	// prefix lengths may be supported by the BPF datapath without causing
	// BPF code generation to exceed the verifier instruction limit.
	// It applies to Linux versions that lack support for LPM, ie < v4.11.
	//
	// This is based upon the defines in bpf/ep_config.h, which in turn
	// are derived by building the bpf/ directory and running the script
	// test/bpf/verifier-test.sh, then adjusting the number of unique
	// prefix lengths until the script passes.
	maxPrefixLengths6 = 4
	maxPrefixLengths4 = 18
)

// Key implements the bpf.MapKey interface.
//
// Must be in sync with struct ipcache_key in <bpf/lib/maps.h>
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=github.com/cilium/cilium/pkg/bpf.MapKey
type Key struct {
	Prefixlen uint32 `align:"lpm_key"`
	Pad1      uint16 `align:"pad1"`
	Pad2      uint8  `align:"pad2"`
	Family    uint8  `align:"family"`
	// represents both IPv6 and IPv4 (in the lowest four bytes)
	IP types.IPv6 `align:"$union0"`
}

// GetKeyPtr returns the unsafe pointer to the BPF key
func (k *Key) GetKeyPtr() unsafe.Pointer { return unsafe.Pointer(k) }

// NewValue returns a new empty instance of the structure representing the BPF
// map value
func (k Key) NewValue() bpf.MapValue { return &RemoteEndpointInfo{} }

func getStaticPrefixBits() uint32 {
	staticMatchSize := unsafe.Sizeof(Key{})
	staticMatchSize -= unsafe.Sizeof(Key{}.Prefixlen)
	staticMatchSize -= unsafe.Sizeof(Key{}.IP)
	return uint32(staticMatchSize) * 8
}

func (k Key) String() string {
	prefixLen := k.Prefixlen - getStaticPrefixBits()
	switch k.Family {
	case bpf.EndpointKeyIPv4:
		ipStr := net.IP(k.IP[:net.IPv4len]).String()
		return fmt.Sprintf("%s/%d", ipStr, prefixLen)
	case bpf.EndpointKeyIPv6:
		ipStr := k.IP.String()
		return fmt.Sprintf("%s/%d", ipStr, prefixLen)
	}
	return "<unknown>"
}

// getPrefixLen determines the length that should be set inside the Key so that
// the lookup prefix is correct in the BPF map key. The specified 'prefixBits'
// indicates the number of bits in the IP that must match to match the entry in
// the BPF ipcache.
func getPrefixLen(prefixBits int) uint32 {
	return getStaticPrefixBits() + uint32(prefixBits)
}

// NewKey returns an Key based on the provided IP address and mask. The address
// family is automatically detected
func NewKey(ip net.IP, mask net.IPMask) Key {
	result := Key{}

	ones, _ := mask.Size()
	if ip4 := ip.To4(); ip4 != nil {
		if mask == nil {
			ones = net.IPv4len * 8
		}
		result.Prefixlen = getPrefixLen(ones)
		result.Family = bpf.EndpointKeyIPv4
		copy(result.IP[:], ip4)
	} else {
		if mask == nil {
			ones = net.IPv6len * 8
		}
		result.Prefixlen = getPrefixLen(ones)
		result.Family = bpf.EndpointKeyIPv6
		copy(result.IP[:], ip)
	}

	return result
}

// RemoteEndpointInfo implements the bpf.MapValue interface. It contains the
// security identity of a remote endpoint.
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=github.com/cilium/cilium/pkg/bpf.MapValue
type RemoteEndpointInfo struct {
	SecurityIdentity uint32     `align:"sec_label"`
	TunnelEndpoint   types.IPv4 `align:"tunnel_endpoint"`
	Key              uint8      `align:"key"`
}

func (v *RemoteEndpointInfo) String() string {
	return fmt.Sprintf("identity=%d encryptkey=%d tunnelendpoint=%s",
		v.SecurityIdentity, v.Key, v.TunnelEndpoint)
}

// GetValuePtr returns the unsafe pointer to the BPF value.
func (v *RemoteEndpointInfo) GetValuePtr() unsafe.Pointer { return unsafe.Pointer(v) }

// Map represents an IPCache BPF map.
type Map struct {
	bpf.Map

	// detectDeleteSupport is used to initialize 'supportsDelete' the first
	// time that a delete is issued from the datapath.
	detectDeleteSupport sync.Once

	// deleteSupport is set to 'true' initially, then is updated to set
	// whether the underlying kernel supports delete operations on the map
	// the first time that supportsDelete() is called.
	deleteSupport bool

	// dumpSupport is set to 'true' initially, then is updated to set
	// whether the underlying kernel supports dump operations on the map
	// the first time that supportsDelete() or supportsDump() is called.
	dumpSupport bool
}

// NewMap instantiates a Map.
func NewMap(name string) *Map {
	return &Map{
		Map: *bpf.NewMap(
			name,
			bpf.MapTypeLPMTrie,
			&Key{},
			int(unsafe.Sizeof(Key{})),
			&RemoteEndpointInfo{},
			int(unsafe.Sizeof(RemoteEndpointInfo{})),
			MaxEntries,
			bpf.BPF_F_NO_PREALLOC, 0,
			bpf.ConvertKeyValue,
		).WithCache().WithPressureMetric(),
		deleteSupport: true,
		dumpSupport:   true,
	}
}

// delete removes a key from the ipcache BPF map, and returns whether the
// kernel supports the delete operation (true) or not (false), and any error
// that may have occurred while attempting to delete the entry.
//
// If "overwrite" is true, then if delete is not supported the entry's value
// will be overwritten with zeroes to signify that it's an invalid entry.
func (m *Map) delete(k bpf.MapKey, overwrite bool) (bool, error) {
	err := m.Delete(k)
	var errno unix.Errno
	if ok := errors.As(err, &errno); ok && errno == unix.ENOSYS {
		if overwrite {
			// Older kernels do not support deletion of LPM map entries so zero out
			// the entry instead of attempting a deletion
			return false, m.Update(k, &RemoteEndpointInfo{})
		}
		return false, nil
	}
	return true, err
}

// DeleteWithOverwrite removes a key from the ipcache BPF map.
// If delete is not supported, the entry's value will be overwritten with
// zeroes to signify that it's an invalid entry.
func (m *Map) DeleteWithOverwrite(k bpf.MapKey) error {
	_, err := m.delete(k, true)
	return err
}

// GetMaxPrefixLengths determines how many unique prefix lengths are supported
// simultaneously based on the underlying BPF map type in use.
func (m *Map) GetMaxPrefixLengths() (ipv6, ipv4 int) {
	if BackedByLPM() {
		return net.IPv6len*8 + 1, net.IPv4len*8 + 1
	}
	return maxPrefixLengths6, maxPrefixLengths4
}

func (m *Map) supportsDelete() bool {
	m.detectDeleteSupport.Do(func() {
		// Entry is invalid because IPCache needs a family specified.
		invalidEntry := &Key{}
		m.deleteSupport, _ = m.delete(invalidEntry, false)
		log.Debugf("Detected IPCache delete operation support: %t", m.deleteSupport)

		// Detect dump support
		err := m.Dump(map[string][]string{})
		m.dumpSupport = err == nil
		log.Debugf("Detected IPCache dump operation support: %t", m.dumpSupport)

		// In addition to delete support, ability to dump the map is
		// also required in order to run the garbage collector which
		// will iterate over the map and delete entries.
		if m.deleteSupport {
			m.deleteSupport = m.dumpSupport
		}

		if !m.deleteSupport {
			log.Infof("Periodic IPCache map swap will occur due to lack of kernel support for LPM delete operation. Upgrade to Linux 4.15 or higher to avoid this.")
		}
	})
	return m.deleteSupport
}

func (m *Map) supportsDump() bool {
	m.supportsDelete()
	return m.dumpSupport
}

// SupportsDelete determines whether the underlying kernel map type supports
// the delete operation.
func SupportsDelete() bool {
	return IPCache.supportsDelete()
}

// SupportsDump determines whether the underlying kernel map type supports
// the dump operation.
func SupportsDump() bool {
	return IPCache.supportsDump()
}

// BackedByLPM returns true if the IPCache is backed by a proper LPM
// implementation (provided by Linux kernels 4.11 or later), false otherwise.
func BackedByLPM() bool {
	return IPCache.MapType == bpf.MapTypeLPMTrie
}

var (
	// IPCache is a mapping of all endpoint IPs in the cluster which this
	// Cilium agent is a part of to their corresponding security identities.
	// It is a singleton; there is only one such map per agent.
	IPCache = NewMap(Name)
)

// Reopen attempts to close and re-open the IPCache map at the standard path
// on the filesystem.
func Reopen() error {
	return IPCache.Map.Reopen()
}

// Function to update IPCache map with VTEP CIDR
func UpdateIPCacheVTEPMapping(newCIDR *cidr.CIDR, newTunnelEndpoint net.IP,
	securityIdentity uint32, encryptKey uint8) error {

	key := NewKey(newCIDR.IP, newCIDR.Mask)

	value := RemoteEndpointInfo{
		SecurityIdentity: securityIdentity,
		Key:              encryptKey,
	}
	if ip4 := newTunnelEndpoint.To4(); ip4 != nil {
		copy(value.TunnelEndpoint[:], ip4)
	}

	return IPCache.Update(&key, &value)

}
<|endoftext|>


<commit_before>// Copyright 2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package securelaunch takes integrity measurements before launching the target system.
package securelaunch

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/u-root/u-root/pkg/boot/diskboot"
	"github.com/u-root/u-root/pkg/storage"
)

/* used to store all block devices returned from a call to storage.GetBlockStats */
var storageBlkDevices []storage.BlockDev

/*
 * if kernel cmd line has uroot.uinitargs=-d, debug fn is enabled.
 * kernel cmdline is checked in sluinit.
 */
var Debug = func(string, ...interface{}) {}

/*
 * WriteToFile writes a byte slice to a target file on an
 * already mounted disk and returns the target file path.
 *
 * defFileName is default dst file name, only used if user doesn't provide one.
 */
func WriteToFile(data []byte, dst, defFileName string) (string, error) {

	// make sure dst is an absolute file path
	if !filepath.IsAbs(dst) {
		return "", fmt.Errorf("dst =%s Not an absolute path ", dst)
	}

	// target is the full absolute path where []byte will be written to
	target := dst
	dstInfo, err := os.Stat(dst)
	if err == nil && dstInfo.IsDir() {
		Debug("No file name provided. Adding it now. old target=%s", target)
		target = filepath.Join(dst, defFileName)
		Debug("New target=%s", target)
	}

	Debug("target=%s", target)
	err = ioutil.WriteFile(target, data, 0644)
	if err != nil {
		return "", fmt.Errorf("failed to write date to file =%s, err=%v", target, err)
	}
	Debug("WriteToFile exit w success data written to target=%s", target)
	return target, nil
}

/*
 * GetMountedFilePath returns a file path corresponding to a <device_identifier>:<path> user input format.
 * <device_identifier> may be a Linux block device identifier like sda or a FS UUID.
 *
 * NOTE: Caller's responsbility to unmount this..use return var mountPath to unmount in caller.
 */
func GetMountedFilePath(inputVal string, flags uintptr) (string, string, error) {
	s := strings.Split(inputVal, ":")
	if len(s) != 2 {
		return "", "", fmt.Errorf("%s: Usage: <block device identifier>:<path>", inputVal)
	}

	// s[0] can be sda or UUID. if UUID, then we need to find its name
	deviceId := s[0]
	if !strings.HasPrefix(deviceId, "sd") {
		if e := getBlkInfo(); e != nil {
			return "", "", fmt.Errorf("getBlkInfo err=%s", e)
		}
		devices := storage.PartitionsByFsUUID(storageBlkDevices, s[0]) // []BlockDev
		for _, device := range devices {
			Debug("device =%s with fsuuid=%s", device.Name, s[0])
			deviceId = device.Name
		}
	}

	devicePath := filepath.Join("/dev", deviceId) // assumes deviceId is sda, devicePath=/dev/sda
	Debug("Attempting to mount %s", devicePath)
	dev, err := diskboot.FindDevice(devicePath, flags) // FindDevice fn mounts devicePath=/dev/sda.
	if err != nil {
		return "", "", fmt.Errorf("failed to mount %v , flags=%v, err=%v", devicePath, flags, err)
	}

	Debug("Mounted %s", devicePath)
	fPath := filepath.Join(dev.MountPoint.Path, s[1]) // mountPath=/tmp/path/to/target/file if /dev/sda mounted on /tmp
	return fPath, dev.MountPoint.Path, nil
}

/*
 * getBlkInfo calls storage package to get information on all block devices.
 * The information is stored in a global variable 'storageBlkDevices'
 * If the global variable is already non-zero, we skip the call to storage package.
 *
 * In debug mode, it also prints names and UUIDs for all devices.
 */
func getBlkInfo() error {
	if len(storageBlkDevices) == 0 {
		var err error
		storageBlkDevices, err = storage.GetBlockStats()
		if err != nil {
			log.Printf("getBlkInfo: storage.GetBlockStats err=%v. Exiting", err)
			return err
		}
	}

	for k, d := range storageBlkDevices {
		Debug("block device #%d, Name=%s, FsUUID=%s", k, d.Name, d.FsUUID)
	}
	return nil
}
<commit_msg>securelaunch: helpers: check if no block devices exist<commit_after>// Copyright 2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package securelaunch takes integrity measurements before launching the target system.
package securelaunch

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/u-root/u-root/pkg/boot/diskboot"
	"github.com/u-root/u-root/pkg/storage"
)

/* used to store all block devices returned from a call to storage.GetBlockStats */
var storageBlkDevices []storage.BlockDev

/*
 * if kernel cmd line has uroot.uinitargs=-d, debug fn is enabled.
 * kernel cmdline is checked in sluinit.
 */
var Debug = func(string, ...interface{}) {}

/*
 * WriteToFile writes a byte slice to a target file on an
 * already mounted disk and returns the target file path.
 *
 * defFileName is default dst file name, only used if user doesn't provide one.
 */
func WriteToFile(data []byte, dst, defFileName string) (string, error) {

	// make sure dst is an absolute file path
	if !filepath.IsAbs(dst) {
		return "", fmt.Errorf("dst =%s Not an absolute path ", dst)
	}

	// target is the full absolute path where []byte will be written to
	target := dst
	dstInfo, err := os.Stat(dst)
	if err == nil && dstInfo.IsDir() {
		Debug("No file name provided. Adding it now. old target=%s", target)
		target = filepath.Join(dst, defFileName)
		Debug("New target=%s", target)
	}

	Debug("target=%s", target)
	err = ioutil.WriteFile(target, data, 0644)
	if err != nil {
		return "", fmt.Errorf("failed to write date to file =%s, err=%v", target, err)
	}
	Debug("WriteToFile exit w success data written to target=%s", target)
	return target, nil
}

/*
 * GetMountedFilePath returns a file path corresponding to a <device_identifier>:<path> user input format.
 * <device_identifier> may be a Linux block device identifier like sda or a FS UUID.
 *
 * NOTE: Caller's responsbility to unmount this..use return var mountPath to unmount in caller.
 */
func GetMountedFilePath(inputVal string, flags uintptr) (string, string, error) {
	s := strings.Split(inputVal, ":")
	if len(s) != 2 {
		return "", "", fmt.Errorf("%s: Usage: <block device identifier>:<path>", inputVal)
	}

	// s[0] can be sda or UUID. if UUID, then we need to find its name
	deviceId := s[0]
	if !strings.HasPrefix(deviceId, "sd") {
		if e := getBlkInfo(); e != nil {
			return "", "", fmt.Errorf("getBlkInfo err=%s", e)
		}
		devices := storage.PartitionsByFsUUID(storageBlkDevices, s[0]) // []BlockDev
		for _, device := range devices {
			Debug("device =%s with fsuuid=%s", device.Name, s[0])
			deviceId = device.Name
		}
	}

	devicePath := filepath.Join("/dev", deviceId) // assumes deviceId is sda, devicePath=/dev/sda
	Debug("Attempting to mount %s", devicePath)
	dev, err := diskboot.FindDevice(devicePath, flags) // FindDevice fn mounts devicePath=/dev/sda.
	if err != nil {
		return "", "", fmt.Errorf("failed to mount %v , flags=%v, err=%v", devicePath, flags, err)
	}

	Debug("Mounted %s", devicePath)
	fPath := filepath.Join(dev.MountPoint.Path, s[1]) // mountPath=/tmp/path/to/target/file if /dev/sda mounted on /tmp
	return fPath, dev.MountPoint.Path, nil
}

/*
 * getBlkInfo calls storage package to get information on all block devices.
 * The information is stored in a global variable 'storageBlkDevices'
 * If the global variable is already non-zero, we skip the call to storage package.
 *
 * In debug mode, it also prints names and UUIDs for all devices.
 */
func getBlkInfo() error {
	if len(storageBlkDevices) == 0 {
		var err error
		Debug("getBlkInfo: expensive function call to get block stats from storage pkg")
		storageBlkDevices, err = storage.GetBlockStats()
		if err != nil {
			return fmt.Errorf("getBlkInfo: storage.GetBlockStats err=%v. Exiting", err)
		}
		// no block devices exist on the system.
		if len(storageBlkDevices) == 0 {
			return fmt.Errorf("getBlkInfo: no block devices found")
		}
		// print the debug info only when expensive call to storage is made
		for k, d := range storageBlkDevices {
			Debug("block device #%d, Name=%s, FSType=%s, FsUUID=%s", k, d.Name, d.FSType, d.FsUUID)
		}
		return nil
	}
	Debug("getBlkInfo: noop")
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kernel

import (
	"fmt"
	"math/rand"
	"sync"
)

// syslog represents a sentry-global kernel log.
//
// Currently, it contains only fun messages for a dmesg easter egg.
//
// +stateify savable
type syslog struct {
	// mu protects the below.
	mu sync.Mutex `state:"nosave"`

	// msg is the syslog message buffer. It is lazily initialized.
	msg []byte
}

// Log returns a copy of the syslog.
func (s *syslog) Log() []byte {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.msg != nil {
		// Already initialized, just return a copy.
		o := make([]byte, len(s.msg))
		copy(o, s.msg)
		return o
	}

	// Not initialized, create message.
	allMessages := []string{
		"Synthesizing system calls...",
		"Mounting deweydecimalfs...",
		"Moving files to filing cabinet...",
		"Digging up root...",
		"Constructing home...",
		"Segmenting fault lines...",
		"Creating bureaucratic processes...",
		"Searching for needles in stacks...",
		"Preparing for the zombie uprising...",
		"Feeding the init monster...",
		"Creating cloned children...",
		"Daemonizing children...",
		"Waiting for children...",
		"Gathering forks...",
		"Committing treasure map to memory...",
		"Reading process obituaries...",
		"Searching for socket adapter...",
		"Creating process schedule...",
		"Generating random numbers by fair dice roll...",
		"Rewriting operating system in Javascript...",
		"Consulting tar man page...",
		"Forking spaghetti code...",
		"Checking naughty and nice process list...",
		"Checking naughty and nice process list...", // Check it up to twice.
		"Granting licence to kill(2)...",            // British spelling for British movie.
		"Letting the watchdogs out...",
	}

	selectMessage := func() string {
		i := rand.Intn(len(allMessages))
		m := allMessages[i]

		// Delete the selected message.
		allMessages[i] = allMessages[len(allMessages)-1]
		allMessages = allMessages[:len(allMessages)-1]

		return m
	}

	const format = "<6>[%11.6f] %s\n"

	s.msg = append(s.msg, []byte(fmt.Sprintf(format, 0.0, "Starting gVisor..."))...)

	time := 0.1
	for i := 0; i < 10; i++ {
		time += rand.Float64() / 2
		s.msg = append(s.msg, []byte(fmt.Sprintf(format, time, selectMessage()))...)
	}

	time += rand.Float64() / 2
	s.msg = append(s.msg, []byte(fmt.Sprintf(format, time, "Ready!"))...)

	// Return a copy.
	o := make([]byte, len(s.msg))
	copy(o, s.msg)
	return o
}
<commit_msg>sentry/kernel: add syslog message<commit_after>// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kernel

import (
	"fmt"
	"math/rand"
	"sync"
)

// syslog represents a sentry-global kernel log.
//
// Currently, it contains only fun messages for a dmesg easter egg.
//
// +stateify savable
type syslog struct {
	// mu protects the below.
	mu sync.Mutex `state:"nosave"`

	// msg is the syslog message buffer. It is lazily initialized.
	msg []byte
}

// Log returns a copy of the syslog.
func (s *syslog) Log() []byte {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.msg != nil {
		// Already initialized, just return a copy.
		o := make([]byte, len(s.msg))
		copy(o, s.msg)
		return o
	}

	// Not initialized, create message.
	allMessages := []string{
		"Synthesizing system calls...",
		"Mounting deweydecimalfs...",
		"Moving files to filing cabinet...",
		"Digging up root...",
		"Constructing home...",
		"Segmenting fault lines...",
		"Creating bureaucratic processes...",
		"Searching for needles in stacks...",
		"Preparing for the zombie uprising...",
		"Feeding the init monster...",
		"Creating cloned children...",
		"Daemonizing children...",
		"Waiting for children...",
		"Gathering forks...",
		"Committing treasure map to memory...",
		"Reading process obituaries...",
		"Searching for socket adapter...",
		"Creating process schedule...",
		"Generating random numbers by fair dice roll...",
		"Rewriting operating system in Javascript...",
		"Reticulating splines...",
		"Consulting tar man page...",
		"Forking spaghetti code...",
		"Checking naughty and nice process list...",
		"Checking naughty and nice process list...", // Check it up to twice.
		"Granting licence to kill(2)...",            // British spelling for British movie.
		"Letting the watchdogs out...",
	}

	selectMessage := func() string {
		i := rand.Intn(len(allMessages))
		m := allMessages[i]

		// Delete the selected message.
		allMessages[i] = allMessages[len(allMessages)-1]
		allMessages = allMessages[:len(allMessages)-1]

		return m
	}

	const format = "<6>[%11.6f] %s\n"

	s.msg = append(s.msg, []byte(fmt.Sprintf(format, 0.0, "Starting gVisor..."))...)

	time := 0.1
	for i := 0; i < 10; i++ {
		time += rand.Float64() / 2
		s.msg = append(s.msg, []byte(fmt.Sprintf(format, time, selectMessage()))...)
	}

	time += rand.Float64() / 2
	s.msg = append(s.msg, []byte(fmt.Sprintf(format, time, "Ready!"))...)

	// Return a copy.
	o := make([]byte, len(s.msg))
	copy(o, s.msg)
	return o
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2017 Red Hat, Inc.
 *
 */

package virthandler

import (
	"io/ioutil"
	"os"
	"time"

	"github.com/golang/mock/gomock"
	. "github.com/onsi/ginkgo"
	"github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/cache/testing"
	"k8s.io/client-go/tools/record"

	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes/fake"

	"kubevirt.io/kubevirt/pkg/api/v1"
	"kubevirt.io/kubevirt/pkg/kubecli"
	"kubevirt.io/kubevirt/pkg/log"
	"kubevirt.io/kubevirt/pkg/precond"
	"kubevirt.io/kubevirt/pkg/testutils"
	"kubevirt.io/kubevirt/pkg/virt-handler/cmd-client"
	"kubevirt.io/kubevirt/pkg/virt-launcher"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
	"kubevirt.io/kubevirt/pkg/watchdog"
)

var _ = Describe("VM", func() {
	var client *cmdclient.MockLauncherClient
	var vmInterface *kubecli.MockVMInterface
	var virtClient *kubecli.MockKubevirtClient

	var ctrl *gomock.Controller
	var controller *VirtualMachineController
	var vmSource *framework.FakeControllerSource
	var vmInformer cache.SharedIndexInformer
	var domainSource *framework.FakeControllerSource
	var domainInformer cache.SharedIndexInformer
	var gracefulShutdownInformer cache.SharedIndexInformer
	var mockQueue *testutils.MockWorkQueue
	var mockWatchdog *MockWatchdog
	var mockGracefulShutdown *MockGracefulShutdown

	var vmFeeder *testutils.VirtualMachineFeeder
	var domainFeeder *testutils.DomainFeeder

	var recorder record.EventRecorder

	var err error
	var shareDir string
	var stop chan struct{}

	log.Log.SetIOWriter(GinkgoWriter)

	BeforeEach(func() {
		stop = make(chan struct{})
		shareDir, err = ioutil.TempDir("", "kubevirt-share")
		Expect(err).ToNot(HaveOccurred())

		host := "master"

		Expect(err).ToNot(HaveOccurred())

		vmInformer, vmSource = testutils.NewFakeInformerFor(&v1.VirtualMachine{})
		domainInformer, domainSource = testutils.NewFakeInformerFor(&api.Domain{})
		gracefulShutdownInformer, _ = testutils.NewFakeInformerFor(&api.Domain{})
		recorder = record.NewFakeRecorder(100)

		ctrl = gomock.NewController(GinkgoT())
		virtClient = kubecli.NewMockKubevirtClient(ctrl)
		vmInterface = kubecli.NewMockVMInterface(ctrl)
		virtClient.EXPECT().VM(metav1.NamespaceDefault).Return(vmInterface).AnyTimes()

		mockWatchdog = &MockWatchdog{shareDir}
		mockGracefulShutdown = &MockGracefulShutdown{shareDir}

		controller = NewController(recorder,
			virtClient,
			host,
			shareDir,
			vmInformer,
			domainInformer,
			gracefulShutdownInformer)

		client = cmdclient.NewMockLauncherClient(ctrl)
		sockFile := cmdclient.SocketFromNamespaceName(shareDir, "default", "testvm")
		controller.addLauncherClient(client, sockFile)

		mockQueue = testutils.NewMockWorkQueue(controller.Queue)
		controller.Queue = mockQueue

		vmFeeder = testutils.NewVirtualMachineFeeder(mockQueue, vmSource)
		domainFeeder = testutils.NewDomainFeeder(mockQueue, domainSource)

		go vmInformer.Run(stop)
		go domainInformer.Run(stop)
		go gracefulShutdownInformer.Run(stop)
		Expect(cache.WaitForCacheSync(stop, vmInformer.HasSynced, domainInformer.HasSynced, gracefulShutdownInformer.HasSynced)).To(BeTrue())
	})

	AfterEach(func() {
		close(stop)
		ctrl.Finish()
		os.RemoveAll(shareDir)
	})

	initGracePeriodHelper := func(gracePeriod int64, vm *v1.VirtualMachine, dom *api.Domain) {
		vm.Spec.TerminationGracePeriodSeconds = &gracePeriod
		dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionGracePeriodSeconds = gracePeriod
	}

	Context("VM controller gets informed about a Domain change through the Domain controller", func() {

		It("should delete non-running Domains if no cluster wide equivalent and no grace period info exists", func() {
			domain := api.NewMinimalDomain("testvm")
			domainFeeder.Add(domain)

			client.EXPECT().Ping()
			client.EXPECT().KillVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			client.EXPECT().Close()
			controller.Execute()
		})

		It("should delete running Domains if no cluster wide equivalent exists and no grace period info exists", func() {
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running
			domainFeeder.Add(domain)

			client.EXPECT().Ping()
			client.EXPECT().KillVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			client.EXPECT().Close()

			controller.Execute()
		})

		It("should perform cleanup of local ephemeral data if domain and vm are deleted", func() {
			mockQueue.Add("default/testvm")
			client.EXPECT().Close()
			controller.Execute()
		})

		It("should attempt graceful shutdown of Domain if trigger file exists.", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.Status.Phase = v1.Running

			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running

			initGracePeriodHelper(1, vm, domain)
			mockWatchdog.CreateFile(vm)
			mockGracefulShutdown.TriggerShutdown(vm)

			client.EXPECT().Ping()
			client.EXPECT().ShutdownVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			domainFeeder.Add(domain)

			controller.Execute()
		}, 3)

		It("should attempt graceful shutdown of Domain if no cluster wide equivalent exists", func() {
			vm := v1.NewMinimalVM("testvm")
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running

			initGracePeriodHelper(1, vm, domain)
			mockWatchdog.CreateFile(vm)

			client.EXPECT().Ping()
			client.EXPECT().ShutdownVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			domainFeeder.Add(domain)

			controller.Execute()
		}, 3)

		It("should attempt force terminate Domain if grace period expires", func() {
			vm := v1.NewMinimalVM("testvm")
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running

			initGracePeriodHelper(1, vm, domain)
			metav1.Now()
			now := metav1.Time{Time: time.Unix(time.Now().UTC().Unix()-3, 0)}
			domain.Spec.Metadata.KubeVirt.GracePeriod.DeletionTimestamp = &now

			mockWatchdog.CreateFile(vm)
			mockGracefulShutdown.TriggerShutdown(vm)

			client.EXPECT().Ping()
			client.EXPECT().KillVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			client.EXPECT().Close()
			domainFeeder.Add(domain)

			controller.Execute()
		}, 3)

		It("should immediately kill domain with grace period of 0", func() {
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running
			vm := v1.NewMinimalVM("testvm")

			initGracePeriodHelper(0, vm, domain)
			mockWatchdog.CreateFile(vm)
			mockGracefulShutdown.TriggerShutdown(vm)

			client.EXPECT().Ping()
			client.EXPECT().KillVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			client.EXPECT().Close()
			domainFeeder.Add(domain)
			controller.Execute()
		}, 3)

		It("should re-enqueue if the Key is unparseable", func() {
			Expect(mockQueue.Len()).Should(Equal(0))
			mockQueue.Add("a/b/c/d/e")
			controller.Execute()
			Expect(mockQueue.NumRequeues("a/b/c/d/e")).To(Equal(1))
		})

		It("should create the Domain if it sees the first time on a new VM", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Scheduled

			mockWatchdog.CreateFile(vm)
			vmFeeder.Add(vm)

			client.EXPECT().SyncVirtualMachine(vm)

			controller.Execute()
		})

		It("should update from Scheduled to Running, if it sees a running Domain", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Scheduled

			updatedVM := vm.DeepCopy()
			updatedVM.Status.Phase = v1.Running

			mockWatchdog.CreateFile(vm)
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running
			vmFeeder.Add(vm)
			domainFeeder.Add(domain)

			vmInterface.EXPECT().Update(updatedVM)

			node := &k8sv1.Node{
				Status: k8sv1.NodeStatus{
					Addresses: []k8sv1.NodeAddress{
						{
							Type:    k8sv1.NodeInternalIP,
							Address: "127.0.0.1",
						},
					},
				},
			}
			fakeClient := fake.NewSimpleClientset(node).CoreV1()
			virtClient.EXPECT().CoreV1().Return(fakeClient).AnyTimes()

			controller.Execute()
		})

		It("should detect a missing watchdog file and report the error on the VM", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Scheduled

			vmFeeder.Add(vm)
			vmInterface.EXPECT().Update(gomock.Any()).Do(func(vm *v1.VirtualMachine) {
				Expect(vm.Status.Conditions).To(HaveLen(1))
			})
			controller.Execute()
		})

		It("should remove an error condition if a synchronization run succeeds", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Scheduled
			vm.Status.Conditions = []v1.VirtualMachineCondition{
				{
					Type:   v1.VirtualMachineSynchronized,
					Status: k8sv1.ConditionFalse,
				},
			}

			updatedVM := vm.DeepCopy()
			updatedVM.Status.Conditions = nil

			mockWatchdog.CreateFile(vm)
			vmFeeder.Add(vm)

			client.EXPECT().SyncVirtualMachine(vm)
			vmInterface.EXPECT().Update(updatedVM)

			controller.Execute()
		})

		table.DescribeTable("should leave the VM alone if it is in the final phase", func(phase v1.VMPhase) {
			vm := v1.NewMinimalVM("testvm")
			vm.Status.Phase = phase
			vmFeeder.Add(vm)
			controller.Execute()
			// expect no errors and no mock interactions
			Expect(mockQueue.NumRequeues("default/testvm")).To(Equal(0))
		},
			table.Entry("succeeded", v1.Succeeded),
			table.Entry("failed", v1.Failed),
		)
	})
})

type MockGracefulShutdown struct {
	baseDir string
}

func (m *MockGracefulShutdown) TriggerShutdown(vm *v1.VirtualMachine) {
	Expect(os.MkdirAll(virtlauncher.GracefulShutdownTriggerDir(m.baseDir), os.ModePerm)).To(Succeed())

	namespace := precond.MustNotBeEmpty(vm.GetObjectMeta().GetNamespace())
	domain := precond.MustNotBeEmpty(vm.GetObjectMeta().GetName())
	triggerFile := virtlauncher.GracefulShutdownTriggerFromNamespaceName(m.baseDir, namespace, domain)
	err := virtlauncher.GracefulShutdownTriggerInitiate(triggerFile)
	Expect(err).NotTo(HaveOccurred())
}

type MockWatchdog struct {
	baseDir string
}

func (m *MockWatchdog) CreateFile(vm *v1.VirtualMachine) {
	Expect(os.MkdirAll(watchdog.WatchdogFileDirectory(m.baseDir), os.ModePerm)).To(Succeed())
	err := watchdog.WatchdogFileUpdate(
		watchdog.WatchdogFileFromNamespaceName(m.baseDir, vm.ObjectMeta.Namespace, vm.ObjectMeta.Name),
	)
	Expect(err).NotTo(HaveOccurred())
}
<commit_msg>additional virt-handler unit tests<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2017 Red Hat, Inc.
 *
 */

package virthandler

import (
	"io/ioutil"
	"os"
	"time"

	"github.com/golang/mock/gomock"
	. "github.com/onsi/ginkgo"
	"github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/cache/testing"
	"k8s.io/client-go/tools/record"

	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes/fake"

	"kubevirt.io/kubevirt/pkg/api/v1"
	"kubevirt.io/kubevirt/pkg/kubecli"
	"kubevirt.io/kubevirt/pkg/log"
	"kubevirt.io/kubevirt/pkg/precond"
	"kubevirt.io/kubevirt/pkg/testutils"
	"kubevirt.io/kubevirt/pkg/virt-handler/cmd-client"
	"kubevirt.io/kubevirt/pkg/virt-launcher"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
	"kubevirt.io/kubevirt/pkg/watchdog"
)

var _ = Describe("VM", func() {
	var client *cmdclient.MockLauncherClient
	var vmInterface *kubecli.MockVMInterface
	var virtClient *kubecli.MockKubevirtClient

	var ctrl *gomock.Controller
	var controller *VirtualMachineController
	var vmSource *framework.FakeControllerSource
	var vmInformer cache.SharedIndexInformer
	var domainSource *framework.FakeControllerSource
	var domainInformer cache.SharedIndexInformer
	var gracefulShutdownInformer cache.SharedIndexInformer
	var mockQueue *testutils.MockWorkQueue
	var mockWatchdog *MockWatchdog
	var mockGracefulShutdown *MockGracefulShutdown

	var vmFeeder *testutils.VirtualMachineFeeder
	var domainFeeder *testutils.DomainFeeder

	var recorder record.EventRecorder

	var err error
	var shareDir string
	var stop chan struct{}

	log.Log.SetIOWriter(GinkgoWriter)

	BeforeEach(func() {
		stop = make(chan struct{})
		shareDir, err = ioutil.TempDir("", "kubevirt-share")
		Expect(err).ToNot(HaveOccurred())

		host := "master"

		Expect(err).ToNot(HaveOccurred())

		vmInformer, vmSource = testutils.NewFakeInformerFor(&v1.VirtualMachine{})
		domainInformer, domainSource = testutils.NewFakeInformerFor(&api.Domain{})
		gracefulShutdownInformer, _ = testutils.NewFakeInformerFor(&api.Domain{})
		recorder = record.NewFakeRecorder(100)

		ctrl = gomock.NewController(GinkgoT())
		virtClient = kubecli.NewMockKubevirtClient(ctrl)
		vmInterface = kubecli.NewMockVMInterface(ctrl)
		virtClient.EXPECT().VM(metav1.NamespaceDefault).Return(vmInterface).AnyTimes()

		mockWatchdog = &MockWatchdog{shareDir}
		mockGracefulShutdown = &MockGracefulShutdown{shareDir}

		controller = NewController(recorder,
			virtClient,
			host,
			shareDir,
			vmInformer,
			domainInformer,
			gracefulShutdownInformer,
			1)

		client = cmdclient.NewMockLauncherClient(ctrl)
		sockFile := cmdclient.SocketFromNamespaceName(shareDir, "default", "testvm")
		controller.addLauncherClient(client, sockFile)

		mockQueue = testutils.NewMockWorkQueue(controller.Queue)
		controller.Queue = mockQueue

		vmFeeder = testutils.NewVirtualMachineFeeder(mockQueue, vmSource)
		domainFeeder = testutils.NewDomainFeeder(mockQueue, domainSource)

		go vmInformer.Run(stop)
		go domainInformer.Run(stop)
		go gracefulShutdownInformer.Run(stop)
		Expect(cache.WaitForCacheSync(stop, vmInformer.HasSynced, domainInformer.HasSynced, gracefulShutdownInformer.HasSynced)).To(BeTrue())
	})

	AfterEach(func() {
		close(stop)
		ctrl.Finish()
		os.RemoveAll(shareDir)
	})

	initGracePeriodHelper := func(gracePeriod int64, vm *v1.VirtualMachine, dom *api.Domain) {
		vm.Spec.TerminationGracePeriodSeconds = &gracePeriod
		dom.Spec.Metadata.KubeVirt.GracePeriod.DeletionGracePeriodSeconds = gracePeriod
	}

	Context("VM controller gets informed about a Domain change through the Domain controller", func() {

		It("should delete non-running Domains if no cluster wide equivalent and no grace period info exists", func() {
			domain := api.NewMinimalDomain("testvm")
			domainFeeder.Add(domain)

			client.EXPECT().Ping()
			client.EXPECT().KillVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			client.EXPECT().Close()
			controller.Execute()
		})

		It("should delete running Domains if no cluster wide equivalent exists and no grace period info exists", func() {
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running
			domainFeeder.Add(domain)

			client.EXPECT().Ping()
			client.EXPECT().KillVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			client.EXPECT().Close()

			controller.Execute()
		})

		It("should perform cleanup of local ephemeral data if domain and vm are deleted", func() {
			mockQueue.Add("default/testvm")
			client.EXPECT().Close()
			controller.Execute()
		})

		It("should attempt graceful shutdown of Domain if trigger file exists.", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.Status.Phase = v1.Running

			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running

			initGracePeriodHelper(1, vm, domain)
			mockWatchdog.CreateFile(vm)
			mockGracefulShutdown.TriggerShutdown(vm)

			client.EXPECT().Ping()
			client.EXPECT().ShutdownVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			domainFeeder.Add(domain)

			controller.Execute()
		}, 3)

		It("should attempt graceful shutdown of Domain if no cluster wide equivalent exists", func() {
			vm := v1.NewMinimalVM("testvm")
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running

			initGracePeriodHelper(1, vm, domain)
			mockWatchdog.CreateFile(vm)

			client.EXPECT().Ping()
			client.EXPECT().ShutdownVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			domainFeeder.Add(domain)

			controller.Execute()
		}, 3)

		It("should attempt force terminate Domain if grace period expires", func() {
			vm := v1.NewMinimalVM("testvm")
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running

			initGracePeriodHelper(1, vm, domain)
			metav1.Now()
			now := metav1.Time{Time: time.Unix(time.Now().UTC().Unix()-3, 0)}
			domain.Spec.Metadata.KubeVirt.GracePeriod.DeletionTimestamp = &now

			mockWatchdog.CreateFile(vm)
			mockGracefulShutdown.TriggerShutdown(vm)

			client.EXPECT().Ping()
			client.EXPECT().KillVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			client.EXPECT().Close()
			domainFeeder.Add(domain)

			controller.Execute()
		}, 3)

		It("should immediately kill domain with grace period of 0", func() {
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running
			vm := v1.NewMinimalVM("testvm")

			initGracePeriodHelper(0, vm, domain)
			mockWatchdog.CreateFile(vm)
			mockGracefulShutdown.TriggerShutdown(vm)

			client.EXPECT().Ping()
			client.EXPECT().KillVirtualMachine(v1.NewVMReferenceFromName("testvm"))
			client.EXPECT().Close()
			domainFeeder.Add(domain)
			controller.Execute()
		}, 3)

		It("should re-enqueue if the Key is unparseable", func() {
			Expect(mockQueue.Len()).Should(Equal(0))
			mockQueue.Add("a/b/c/d/e")
			controller.Execute()
			Expect(mockQueue.NumRequeues("a/b/c/d/e")).To(Equal(1))
		})

		It("should create the Domain if it sees the first time on a new VM", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Scheduled

			mockWatchdog.CreateFile(vm)
			vmFeeder.Add(vm)

			client.EXPECT().SyncVirtualMachine(vm)

			controller.Execute()
		})

		It("should update from Scheduled to Running, if it sees a running Domain", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Scheduled

			updatedVM := vm.DeepCopy()
			updatedVM.Status.Phase = v1.Running

			mockWatchdog.CreateFile(vm)
			domain := api.NewMinimalDomain("testvm")
			domain.Status.Status = api.Running
			vmFeeder.Add(vm)
			domainFeeder.Add(domain)

			vmInterface.EXPECT().Update(updatedVM)

			node := &k8sv1.Node{
				Status: k8sv1.NodeStatus{
					Addresses: []k8sv1.NodeAddress{
						{
							Type:    k8sv1.NodeInternalIP,
							Address: "127.0.0.1",
						},
					},
				},
			}
			fakeClient := fake.NewSimpleClientset(node).CoreV1()
			virtClient.EXPECT().CoreV1().Return(fakeClient).AnyTimes()

			controller.Execute()
		})

		It("should move VM from Scheduled to Failed if watchdog file is missing", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Scheduled

			vmFeeder.Add(vm)
			vmInterface.EXPECT().Update(gomock.Any()).Do(func(vm *v1.VirtualMachine) {
				Expect(vm.Status.Phase).To(Equal(v1.Failed))
			})
			controller.Execute()
		})
		It("should move VM from Scheduled to Failed if watchdog file is expired", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Scheduled

			mockWatchdog.CreateFile(vm)
			vmFeeder.Add(vm)
			vmInterface.EXPECT().Update(gomock.Any()).Do(func(vm *v1.VirtualMachine) {
				Expect(vm.Status.Phase).To(Equal(v1.Failed))
			})
			time.Sleep(1500 * time.Millisecond)
			controller.Execute()
		}, 2)

		It("should move VM from Running to Failed if domain does not exist in cache", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Running

			vmFeeder.Add(vm)
			vmInterface.EXPECT().Update(gomock.Any()).Do(func(vm *v1.VirtualMachine) {
				Expect(vm.Status.Phase).To(Equal(v1.Failed))
			})
			controller.Execute()
		})

		It("should remove an error condition if a synchronization run succeeds", func() {
			vm := v1.NewMinimalVM("testvm")
			vm.ObjectMeta.ResourceVersion = "1"
			vm.Status.Phase = v1.Scheduled
			vm.Status.Conditions = []v1.VirtualMachineCondition{
				{
					Type:   v1.VirtualMachineSynchronized,
					Status: k8sv1.ConditionFalse,
				},
			}

			updatedVM := vm.DeepCopy()
			updatedVM.Status.Conditions = nil

			mockWatchdog.CreateFile(vm)
			vmFeeder.Add(vm)

			client.EXPECT().SyncVirtualMachine(vm)
			vmInterface.EXPECT().Update(updatedVM)

			controller.Execute()
		})

		table.DescribeTable("should leave the VM alone if it is in the final phase", func(phase v1.VMPhase) {
			vm := v1.NewMinimalVM("testvm")
			vm.Status.Phase = phase
			vmFeeder.Add(vm)
			controller.Execute()
			// expect no errors and no mock interactions
			Expect(mockQueue.NumRequeues("default/testvm")).To(Equal(0))
		},
			table.Entry("succeeded", v1.Succeeded),
			table.Entry("failed", v1.Failed),
		)
	})
})

type MockGracefulShutdown struct {
	baseDir string
}

func (m *MockGracefulShutdown) TriggerShutdown(vm *v1.VirtualMachine) {
	Expect(os.MkdirAll(virtlauncher.GracefulShutdownTriggerDir(m.baseDir), os.ModePerm)).To(Succeed())

	namespace := precond.MustNotBeEmpty(vm.GetObjectMeta().GetNamespace())
	domain := precond.MustNotBeEmpty(vm.GetObjectMeta().GetName())
	triggerFile := virtlauncher.GracefulShutdownTriggerFromNamespaceName(m.baseDir, namespace, domain)
	err := virtlauncher.GracefulShutdownTriggerInitiate(triggerFile)
	Expect(err).NotTo(HaveOccurred())
}

type MockWatchdog struct {
	baseDir string
}

func (m *MockWatchdog) CreateFile(vm *v1.VirtualMachine) {
	Expect(os.MkdirAll(watchdog.WatchdogFileDirectory(m.baseDir), os.ModePerm)).To(Succeed())
	err := watchdog.WatchdogFileUpdate(
		watchdog.WatchdogFileFromNamespaceName(m.baseDir, vm.ObjectMeta.Namespace, vm.ObjectMeta.Name),
	)
	Expect(err).NotTo(HaveOccurred())
}
<|endoftext|>


<commit_before>package zfs

import (
	"bytes"
	"errors"
	"io"
	"io/ioutil"
	"net/url"

	"github.com/Sirupsen/logrus"
	"github.com/cerana/cerana/acomm"
	"github.com/cerana/cerana/pkg/logrusx"
	"github.com/cerana/cerana/provider"
	"github.com/cerana/cerana/zfs"
)

// MockZFS is a mock ZFS provider.
type MockZFS struct {
	config  *provider.Config
	tracker *acomm.Tracker
	Data    *MockZFSData
}

// MockZFSData is the in-memory data structure for the MockZFS.
type MockZFSData struct {
	Datasets map[string]*Dataset
	Data     map[string][]byte
}

// NewMockZFS creates a new instance of MockZFS.
func NewMockZFS(config *provider.Config, tracker *acomm.Tracker) *MockZFS {
	return &MockZFS{
		config:  config,
		tracker: tracker,
		Data: &MockZFSData{
			Datasets: make(map[string]*Dataset),
			Data:     make(map[string][]byte),
		},
	}
}

// RegisterTasks registers all MockZFS tasks.
func (z *MockZFS) RegisterTasks(server *provider.Server) {
	server.RegisterTask("zfs-clone", z.Clone)
	server.RegisterTask("zfs-create", z.Create)
	server.RegisterTask("zfs-destroy", z.Destroy)
	server.RegisterTask("zfs-exists", z.Exists)
	server.RegisterTask("zfs-get", z.Get)
	server.RegisterTask("zfs-holds", z.Holds)
	server.RegisterTask("zfs-list", z.List)
	server.RegisterTask("zfs-mount", z.Mount)
	server.RegisterTask("zfs-receive", z.Receive)
	server.RegisterTask("zfs-rename", z.Rename)
	server.RegisterTask("zfs-rollback", z.Rollback)
	server.RegisterTask("zfs-send", z.Send)
	server.RegisterTask("zfs-snapshot", z.Snapshot)
	server.RegisterTask("zfs-unmount", z.Unmount)
}

// Clone clones a mock dataset.
func (z *MockZFS) Clone(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CloneArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if args.Origin == "" {
		return nil, nil, errors.New("missing arg: origin")
	}
	if err := fixPropertyTypesFromJSON(args.Properties); err != nil {
		return nil, nil, err
	}

	origin, ok := z.Data.Datasets[args.Origin]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}
	*z.Data.Datasets[args.Name] = *origin
	z.Data.Datasets[args.Name].Name = args.Name
	z.Data.Datasets[args.Name].Properties.Origin = args.Origin
	return &DatasetResult{z.Data.Datasets[args.Name]}, nil, nil
}

// Create creats a mock dataset.
func (z *MockZFS) Create(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CreateArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}
	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	if err := fixPropertyTypesFromJSON(args.Properties); err != nil {
		return nil, nil, err
	}
	_, ok := z.Data.Datasets[args.Name]
	if ok {
		return nil, nil, errors.New("dataset not found")
	}

	z.Data.Datasets[args.Name] = &Dataset{
		Name: args.Name,
		Properties: &zfs.DatasetProperties{
			Type:    args.Type,
			Volsize: args.Volsize,
		},
	}
	return &DatasetResult{z.Data.Datasets[args.Name]}, nil, nil
}

// Destroy destroys a mock dataset.
func (z *MockZFS) Destroy(req *acomm.Request) (interface{}, *url.URL, error) {
	var args DestroyArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}
	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}
	delete(z.Data.Datasets, args.Name)
	delete(z.Data.Data, args.Name)
	return nil, nil, nil
}

// Exists checks whether a mock dataset exists.
func (z *MockZFS) Exists(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	_, ok := z.Data.Datasets[args.Name]
	return &ExistsResult{ok}, nil, nil
}

// Get retrieves a mock dataset.
func (z *MockZFS) Get(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	dataset, ok := z.Data.Datasets[args.Name]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}
	return &DatasetResult{dataset}, nil, nil
}

// Holds retrieves a mock dataset's holds.
func (z *MockZFS) Holds(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}

	return &HoldsResult{[]string{}}, nil, nil
}

// List returns all mock datasets.
func (z *MockZFS) List(req *acomm.Request) (interface{}, *url.URL, error) {
	var args ListArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if len(args.Types) == 0 {
		args.Types = []string{"all"}
	}
	datasets := make([]*Dataset, len(z.Data.Datasets))
	for _, dataset := range z.Data.Datasets {
		for _, t := range args.Types {
			if t == dataset.Properties.Type || t == "all" {
				datasets = append(datasets, dataset)
				break
			}
		}
	}

	return &ListResult{datasets}, nil, nil
}

// Mount mounts a mock dataset.
func (z *MockZFS) Mount(req *acomm.Request) (interface{}, *url.URL, error) {
	var args MountArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}

	return nil, nil, nil
}

// Receive receives mock dataset data and creates a mock dataset.
func (z *MockZFS) Receive(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if req.StreamURL == nil {
		return nil, nil, errors.New("missing request stream-url")
	}

	r, w := io.Pipe()
	go func() {
		defer logrusx.LogReturnedErr(w.Close, nil, "failed to close writer")
		logrusx.LogReturnedErr(func() error {
			return acomm.Stream(w, req.StreamURL)
		}, logrus.Fields{"streamURL": req.StreamURL}, "failed to stream")
	}()

	data, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, nil, err
	}
	z.Data.Data[args.Name] = data
	z.Data.Datasets[args.Name] = &Dataset{Name: args.Name}
	return nil, nil, nil
}

// Rename renames a mock dataset.
func (z *MockZFS) Rename(req *acomm.Request) (interface{}, *url.URL, error) {
	var args RenameArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if args.Origin == "" {
		return nil, nil, errors.New("missing arg: origin")
	}

	origin, ok := z.Data.Datasets[args.Origin]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}
	if _, ok = z.Data.Datasets[args.Name]; ok {
		return nil, nil, errors.New("dataset already exists")
	}

	z.Data.Datasets[args.Name] = origin
	delete(z.Data.Datasets, args.Origin)
	if data, ok := z.Data.Data[args.Origin]; ok {
		z.Data.Data[args.Name] = data
		delete(z.Data.Data, args.Origin)
	}
	return nil, nil, nil
}

// Rollback rolls back to a mock dataset.
func (z *MockZFS) Rollback(req *acomm.Request) (interface{}, *url.URL, error) {
	var args RollbackArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}
	return nil, nil, nil
}

// Send sends mock dataset data.
func (z *MockZFS) Send(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	data, ok := z.Data.Data[args.Name]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}

	reader := bytes.NewReader(data)

	addr, err := z.tracker.NewStreamUnix(z.config.StreamDir("zfs-send"), ioutil.NopCloser(reader))
	if err != nil {
		return nil, nil, err
	}

	return nil, addr, nil
}

// Snapshot snapshots a mock dataset.
func (z *MockZFS) Snapshot(req *acomm.Request) (interface{}, *url.URL, error) {
	var args SnapshotArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if args.SnapName == "" {
		return nil, nil, errors.New("missing arg: snapname")
	}

	dataset, ok := z.Data.Datasets[args.Name]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}

	snapName := args.Name + "@" + args.SnapName
	*z.Data.Datasets[snapName] = *dataset
	z.Data.Datasets[snapName].Name = snapName
	z.Data.Datasets[snapName].Properties.Type = "snapshot"

	return nil, nil, nil
}

// Unmount unmounts a mock dataset.
func (z *MockZFS) Unmount(req *acomm.Request) (interface{}, *url.URL, error) {
	var args UnmountArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}

	return nil, nil, nil
}
<commit_msg>Fix array length in zfs mock list<commit_after>package zfs

import (
	"bytes"
	"errors"
	"io"
	"io/ioutil"
	"net/url"

	"github.com/Sirupsen/logrus"
	"github.com/cerana/cerana/acomm"
	"github.com/cerana/cerana/pkg/logrusx"
	"github.com/cerana/cerana/provider"
	"github.com/cerana/cerana/zfs"
)

// MockZFS is a mock ZFS provider.
type MockZFS struct {
	config  *provider.Config
	tracker *acomm.Tracker
	Data    *MockZFSData
}

// MockZFSData is the in-memory data structure for the MockZFS.
type MockZFSData struct {
	Datasets map[string]*Dataset
	Data     map[string][]byte
}

// NewMockZFS creates a new instance of MockZFS.
func NewMockZFS(config *provider.Config, tracker *acomm.Tracker) *MockZFS {
	return &MockZFS{
		config:  config,
		tracker: tracker,
		Data: &MockZFSData{
			Datasets: make(map[string]*Dataset),
			Data:     make(map[string][]byte),
		},
	}
}

// RegisterTasks registers all MockZFS tasks.
func (z *MockZFS) RegisterTasks(server *provider.Server) {
	server.RegisterTask("zfs-clone", z.Clone)
	server.RegisterTask("zfs-create", z.Create)
	server.RegisterTask("zfs-destroy", z.Destroy)
	server.RegisterTask("zfs-exists", z.Exists)
	server.RegisterTask("zfs-get", z.Get)
	server.RegisterTask("zfs-holds", z.Holds)
	server.RegisterTask("zfs-list", z.List)
	server.RegisterTask("zfs-mount", z.Mount)
	server.RegisterTask("zfs-receive", z.Receive)
	server.RegisterTask("zfs-rename", z.Rename)
	server.RegisterTask("zfs-rollback", z.Rollback)
	server.RegisterTask("zfs-send", z.Send)
	server.RegisterTask("zfs-snapshot", z.Snapshot)
	server.RegisterTask("zfs-unmount", z.Unmount)
}

// Clone clones a mock dataset.
func (z *MockZFS) Clone(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CloneArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if args.Origin == "" {
		return nil, nil, errors.New("missing arg: origin")
	}
	if err := fixPropertyTypesFromJSON(args.Properties); err != nil {
		return nil, nil, err
	}

	origin, ok := z.Data.Datasets[args.Origin]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}
	*z.Data.Datasets[args.Name] = *origin
	z.Data.Datasets[args.Name].Name = args.Name
	z.Data.Datasets[args.Name].Properties.Origin = args.Origin
	return &DatasetResult{z.Data.Datasets[args.Name]}, nil, nil
}

// Create creats a mock dataset.
func (z *MockZFS) Create(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CreateArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}
	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	if err := fixPropertyTypesFromJSON(args.Properties); err != nil {
		return nil, nil, err
	}
	_, ok := z.Data.Datasets[args.Name]
	if ok {
		return nil, nil, errors.New("dataset not found")
	}

	z.Data.Datasets[args.Name] = &Dataset{
		Name: args.Name,
		Properties: &zfs.DatasetProperties{
			Type:    args.Type,
			Volsize: args.Volsize,
		},
	}
	return &DatasetResult{z.Data.Datasets[args.Name]}, nil, nil
}

// Destroy destroys a mock dataset.
func (z *MockZFS) Destroy(req *acomm.Request) (interface{}, *url.URL, error) {
	var args DestroyArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}
	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}
	delete(z.Data.Datasets, args.Name)
	delete(z.Data.Data, args.Name)
	return nil, nil, nil
}

// Exists checks whether a mock dataset exists.
func (z *MockZFS) Exists(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	_, ok := z.Data.Datasets[args.Name]
	return &ExistsResult{ok}, nil, nil
}

// Get retrieves a mock dataset.
func (z *MockZFS) Get(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	dataset, ok := z.Data.Datasets[args.Name]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}
	return &DatasetResult{dataset}, nil, nil
}

// Holds retrieves a mock dataset's holds.
func (z *MockZFS) Holds(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}

	return &HoldsResult{[]string{}}, nil, nil
}

// List returns all mock datasets.
func (z *MockZFS) List(req *acomm.Request) (interface{}, *url.URL, error) {
	var args ListArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if len(args.Types) == 0 {
		args.Types = []string{"all"}
	}
	datasets := make([]*Dataset, 0, len(z.Data.Datasets))
	for _, dataset := range z.Data.Datasets {
		for _, t := range args.Types {
			if t == dataset.Properties.Type || t == "all" {
				datasets = append(datasets, dataset)
				break
			}
		}
	}

	return &ListResult{datasets}, nil, nil
}

// Mount mounts a mock dataset.
func (z *MockZFS) Mount(req *acomm.Request) (interface{}, *url.URL, error) {
	var args MountArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}

	return nil, nil, nil
}

// Receive receives mock dataset data and creates a mock dataset.
func (z *MockZFS) Receive(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if req.StreamURL == nil {
		return nil, nil, errors.New("missing request stream-url")
	}

	r, w := io.Pipe()
	go func() {
		defer logrusx.LogReturnedErr(w.Close, nil, "failed to close writer")
		logrusx.LogReturnedErr(func() error {
			return acomm.Stream(w, req.StreamURL)
		}, logrus.Fields{"streamURL": req.StreamURL}, "failed to stream")
	}()

	data, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, nil, err
	}
	z.Data.Data[args.Name] = data
	z.Data.Datasets[args.Name] = &Dataset{Name: args.Name}
	return nil, nil, nil
}

// Rename renames a mock dataset.
func (z *MockZFS) Rename(req *acomm.Request) (interface{}, *url.URL, error) {
	var args RenameArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if args.Origin == "" {
		return nil, nil, errors.New("missing arg: origin")
	}

	origin, ok := z.Data.Datasets[args.Origin]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}
	if _, ok = z.Data.Datasets[args.Name]; ok {
		return nil, nil, errors.New("dataset already exists")
	}

	z.Data.Datasets[args.Name] = origin
	delete(z.Data.Datasets, args.Origin)
	if data, ok := z.Data.Data[args.Origin]; ok {
		z.Data.Data[args.Name] = data
		delete(z.Data.Data, args.Origin)
	}
	return nil, nil, nil
}

// Rollback rolls back to a mock dataset.
func (z *MockZFS) Rollback(req *acomm.Request) (interface{}, *url.URL, error) {
	var args RollbackArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}
	return nil, nil, nil
}

// Send sends mock dataset data.
func (z *MockZFS) Send(req *acomm.Request) (interface{}, *url.URL, error) {
	var args CommonArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}

	data, ok := z.Data.Data[args.Name]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}

	reader := bytes.NewReader(data)

	addr, err := z.tracker.NewStreamUnix(z.config.StreamDir("zfs-send"), ioutil.NopCloser(reader))
	if err != nil {
		return nil, nil, err
	}

	return nil, addr, nil
}

// Snapshot snapshots a mock dataset.
func (z *MockZFS) Snapshot(req *acomm.Request) (interface{}, *url.URL, error) {
	var args SnapshotArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if args.SnapName == "" {
		return nil, nil, errors.New("missing arg: snapname")
	}

	dataset, ok := z.Data.Datasets[args.Name]
	if !ok {
		return nil, nil, errors.New("dataset not found")
	}

	snapName := args.Name + "@" + args.SnapName
	*z.Data.Datasets[snapName] = *dataset
	z.Data.Datasets[snapName].Name = snapName
	z.Data.Datasets[snapName].Properties.Type = "snapshot"

	return nil, nil, nil
}

// Unmount unmounts a mock dataset.
func (z *MockZFS) Unmount(req *acomm.Request) (interface{}, *url.URL, error) {
	var args UnmountArgs
	if err := req.UnmarshalArgs(&args); err != nil {
		return nil, nil, err
	}

	if args.Name == "" {
		return nil, nil, errors.New("missing arg: name")
	}
	if _, ok := z.Data.Datasets[args.Name]; !ok {
		return nil, nil, errors.New("dataset not found")
	}

	return nil, nil, nil
}
<|endoftext|>


<commit_before>// Package urltitle is a bort IRC bot plugin that extracts titles for URLs
// posted in a channel
package urltitle

import (
	"log"
	"net/http"
	"strings"

	"github.com/ianremmler/bort"
	"github.com/mvdan/xurls"
	"golang.org/x/net/html"
)

var cfg = &Config{}

type Config struct {
	Prefix string
	Suffix string
}

func findNode(node *html.Node, path ...string) *html.Node {
	for i := range path {
		if node == nil {
			return nil
		}
		for node = node.FirstChild; node != nil; node = node.NextSibling {
			if node.Type == html.ElementNode && node.Data == path[i] {
				break
			}
		}
	}
	return node
}

func extractTitle(in, out *bort.Message) error {
	resp, err := http.Get(in.Match)
	if err != nil {
		return nil
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil
	}
	page, err := html.Parse(resp.Body)
	if err != nil {
		return nil
	}
	title := findNode(page, "html", "head", "title")
	if title != nil && title.FirstChild != nil {
		out.Type = bort.PrivMsg
		text := strings.TrimSpace(title.FirstChild.Data)
		text = strings.SplitN(text+"\n", "\n", 2)[0] // first line
		out.Text = cfg.Prefix + text + cfg.Suffix
	}
	return nil
}

func setup() error {
	return bort.GetConfig(&struct{ Urltitle *Config }{cfg})
}

func init() {
	bort.RegisterSetup(setup)
	urlRE, err := xurls.StrictMatchingScheme("http")
	if err != nil {
		log.Println("urltitle: error setting regexp")
		return
	}
	pat := "(" + urlRE.String() + ")"
	if _, err = bort.RegisterMatcher(bort.PrivMsg, pat, extractTitle); err != nil {
		log.Println("urltitle: error registering plugin")
	}
}
<commit_msg>urltitle: Added timeout.<commit_after>// Package urltitle is a bort IRC bot plugin that extracts titles for URLs
// posted in a channel
package urltitle

import (
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/ianremmler/bort"
	"github.com/mvdan/xurls"
	"golang.org/x/net/html"
)

var (
	cfg    = &Config{Timeout: 5}
	client = &http.Client{}
)

type Config struct {
	Prefix  string
	Suffix  string
	Timeout uint
}

func findNode(node *html.Node, path ...string) *html.Node {
	for i := range path {
		if node == nil {
			return nil
		}
		for node = node.FirstChild; node != nil; node = node.NextSibling {
			if node.Type == html.ElementNode && node.Data == path[i] {
				break
			}
		}
	}
	return node
}

func extractTitle(in, out *bort.Message) error {
	resp, err := client.Get(in.Match)
	if err != nil {
		return nil
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil
	}
	page, err := html.Parse(resp.Body)
	if err != nil {
		return nil
	}
	title := findNode(page, "html", "head", "title")
	if title != nil && title.FirstChild != nil {
		out.Type = bort.PrivMsg
		text := strings.TrimSpace(title.FirstChild.Data)
		text = strings.SplitN(text+"\n", "\n", 2)[0] // first line
		out.Text = cfg.Prefix + text + cfg.Suffix
	}
	return nil
}

func setup() error {
	if err := bort.GetConfig(&struct{ Urltitle *Config }{cfg}); err != nil {
		return err
	}
	client.Timeout = time.Duration(cfg.Timeout) * time.Second
	return nil
}

func init() {
	bort.RegisterSetup(setup)
	urlRE, err := xurls.StrictMatchingScheme("http")
	if err != nil {
		log.Println("urltitle: error setting regexp")
		return
	}
	pat := "(" + urlRE.String() + ")"
	if _, err = bort.RegisterMatcher(bort.PrivMsg, pat, extractTitle); err != nil {
		log.Println("urltitle: error registering plugin")
	}
}
<|endoftext|>


<commit_before>package mgos

import (
	"fmt"
	"net/url"
	"testing"
)

type Person struct {
	FirstName  string `mgos:"first_name"`
	LastName   string `mgos:"last_name"`
	Age        int    `mgos:"age"`
	BirthMonth int    `mgos:"birth_month"`
}

func TestFromGetterUsingURLValues(t *testing.T) {
	test := func(q string, expect Person) {
		v, err := url.ParseQuery(q)
		if err != nil {
			t.Errorf("ParseQuery got error: %s", err)
		}

		dest := Person{}
		FromGetter(v, &dest)

		if dest.FirstName != expect.FirstName {
			t.Errorf("FirstName expected (%s) but (%s)", expect.FirstName, dest.FirstName)
		}

		if dest.LastName != expect.LastName {
			t.Errorf("LastName expected (%s) but (%s)", expect.LastName, dest.LastName)
		}

		if dest.Age != expect.Age {
			t.Errorf("Age expected (%d) but (%d)", expect.Age, dest.Age)
		}

		if dest.BirthMonth != expect.BirthMonth {
			t.Errorf("BirthMonth expected (%d) but (%d)", expect.BirthMonth, dest.BirthMonth)
		}
	}

	test("first_name=Tanaka&last_name=Satoshi&age=18", Person{
		FirstName:  "Tanaka",
		LastName:   "Satoshi",
		Age:        18,
		BirthMonth: 0,
	})
}

func ExampleFromGetter() {
	v, _ := url.ParseQuery("first_name=Tanaka&last_name=Satoshi&age=18&birth_month=January")
	dest := Person{}

	FromGetter(v, &dest)

	fmt.Println(dest.FirstName)
	fmt.Println(dest.LastName)
	fmt.Println(dest.Age)
	fmt.Println(dest.BirthMonth)
	// Output:
	// Tanaka
	// Satoshi
	// 18
	// 0
}
<commit_msg>add unsetting fields test<commit_after>package mgos

import (
	"fmt"
	"net/url"
	"testing"
)

type Person struct {
	FirstName  string `mgos:"first_name"`
	LastName   string `mgos:"last_name"`
	Age        int    `mgos:"age"`
	BirthMonth int    `mgos:"birth_month"`
	Hobby      string
}

func TestFromGetterUsingURLValues(t *testing.T) {
	test := func(q string, expect Person) {
		v, err := url.ParseQuery(q)
		if err != nil {
			t.Errorf("ParseQuery got error: %s", err)
		}

		dest := Person{}
		FromGetter(v, &dest)

		if dest.FirstName != expect.FirstName {
			t.Errorf("FirstName expected (%s) but (%s)", expect.FirstName, dest.FirstName)
		}

		if dest.LastName != expect.LastName {
			t.Errorf("LastName expected (%s) but (%s)", expect.LastName, dest.LastName)
		}

		if dest.Age != expect.Age {
			t.Errorf("Age expected (%d) but (%d)", expect.Age, dest.Age)
		}

		if dest.BirthMonth != expect.BirthMonth {
			t.Errorf("BirthMonth expected (%d) but (%d)", expect.BirthMonth, dest.BirthMonth)
		}
	}

	test("first_name=Tanaka&last_name=Satoshi&age=18", Person{
		FirstName:  "Tanaka",
		LastName:   "Satoshi",
		Age:        18,
		BirthMonth: 0,
	})

	test("first_name=Inoue&last_name=Shingo&age=19&", Person{
		FirstName:  "Inoue",
		LastName:   "Shingo",
		Age:        19,
		BirthMonth: 0,
		Hobby:      "tenis",
	})

}

func ExampleFromGetter() {
	v, _ := url.ParseQuery("first_name=Tanaka&last_name=Satoshi&age=18&birth_month=January")
	dest := Person{}

	FromGetter(v, &dest)

	fmt.Println(dest.FirstName)
	fmt.Println(dest.LastName)
	fmt.Println(dest.Age)
	fmt.Println(dest.BirthMonth)
	// Output:
	// Tanaka
	// Satoshi
	// 18
	// 0
}
<|endoftext|>


<commit_before>package volumeattachmentcommands

import (
	"fmt"

	"github.com/jrperritt/rack/commandoptions"
	"github.com/jrperritt/rack/handler"
	"github.com/jrperritt/rack/internal/github.com/codegangsta/cli"
	"github.com/jrperritt/rack/internal/github.com/fatih/structs"
	osVolumeAttach "github.com/jrperritt/rack/internal/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
	osServers "github.com/jrperritt/rack/internal/github.com/rackspace/gophercloud/openstack/compute/v2/servers"
	"github.com/jrperritt/rack/internal/github.com/rackspace/gophercloud/pagination"
	"github.com/jrperritt/rack/output"
	"github.com/jrperritt/rack/util"
)

var list = cli.Command{
	Name:        "list",
	Usage:       util.Usage(commandPrefix, "list", "[--server-id <serverID> | --server-name <serverName> | --stdin id]"),
	Description: "Lists attachments for the given server",
	Action:      actionList,
	Flags:       commandoptions.CommandFlags(flagsList, keysList),
	BashComplete: func(c *cli.Context) {
		commandoptions.CompleteFlags(commandoptions.CommandFlags(flagsList, keysList))
	},
}

func flagsList() []cli.Flag {
	return []cli.Flag{
		cli.StringFlag{
			Name:  "server-id",
			Usage: "[optional; required if `server-name` or `stdin` isn't provided] The server ID of the attachment.",
		},
		cli.StringFlag{
			Name:  "server-name",
			Usage: "[optional; required if `server-id` or `stdin` isn't provided] The server name of the attachment.",
		},
		cli.StringFlag{
			Name:  "stdin",
			Usage: "[optional; required if `server-id` or `server-name` isn't provided] The field being piped into STDIN. Valid values are: id",
		},
	}
}

var keysList = []string{"ID", "Device", "VolumeID", "ServerID"}

type paramsList struct {
	serverID string
}

type commandList handler.Command

func actionList(c *cli.Context) {
	command := &commandList{
		Ctx: &handler.Context{
			CLIContext: c,
		},
	}
	handler.Handle(command)
}

func (command *commandList) Context() *handler.Context {
	return command.Ctx
}

func (command *commandList) Keys() []string {
	return keysList
}

func (command *commandList) ServiceClientType() string {
	return serviceClientType
}

func (command *commandList) HandleFlags(resource *handler.Resource) error {
	resource.Params = &paramsList{}
	return nil
}

func (command *commandList) HandlePipe(resource *handler.Resource, item string) error {
	resource.Params.(*paramsList).serverID = item
	return nil
}

func (command *commandList) HandleSingle(resource *handler.Resource) error {
	ctx := command.Ctx
	var serverID string
	if ctx.CLIContext.IsSet("server-id") {
		if ctx.CLIContext.IsSet("server-name") {
			return fmt.Errorf("Only one of either --server-id or --server-name may be provided.")
		}
		serverID = ctx.CLIContext.String("server-id")
	} else if ctx.CLIContext.IsSet("server-name") {
		name := ctx.CLIContext.String("server-name")
		id, err := osServers.IDFromName(ctx.ServiceClient, name)
		if err != nil {
			return fmt.Errorf("Error converting name [%s] to ID: %s", name, err)
		}
		serverID = id
	} else {
		return output.ErrMissingFlag{Msg: "One of either --server-id or --server-name must be provided."}
	}

	resource.Params.(*paramsList).serverID = serverID
	return nil
}

func (command *commandList) Execute(resource *handler.Resource) {
	params := resource.Params.(*paramsList)
	err := osVolumeAttach.List(command.Ctx.ServiceClient, params.serverID).EachPage(func(page pagination.Page) (bool, error) {
		volumeAttachments, err := osVolumeAttach.ExtractVolumeAttachments(page)
		if err != nil {
			return false, err
		}
		result := make([]map[string]interface{}, len(volumeAttachments))
		for j, volumeAttachment := range volumeAttachments {
			result[j] = structs.Map(volumeAttachment)
		}
		resource.Result = result
		return false, nil
	})
	if err != nil {
		resource.Err = err
		return
	}
}

func (command *commandList) StdinField() string {
	return "id"
}
<commit_msg>stdin variable name change: id -> server-id<commit_after>package volumeattachmentcommands

import (
	"fmt"

	"github.com/jrperritt/rack/commandoptions"
	"github.com/jrperritt/rack/handler"
	"github.com/jrperritt/rack/internal/github.com/codegangsta/cli"
	"github.com/jrperritt/rack/internal/github.com/fatih/structs"
	osVolumeAttach "github.com/jrperritt/rack/internal/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
	osServers "github.com/jrperritt/rack/internal/github.com/rackspace/gophercloud/openstack/compute/v2/servers"
	"github.com/jrperritt/rack/internal/github.com/rackspace/gophercloud/pagination"
	"github.com/jrperritt/rack/output"
	"github.com/jrperritt/rack/util"
)

var list = cli.Command{
	Name:        "list",
	Usage:       util.Usage(commandPrefix, "list", "[--server-id <serverID> | --server-name <serverName> | --stdin id]"),
	Description: "Lists attachments for the given server",
	Action:      actionList,
	Flags:       commandoptions.CommandFlags(flagsList, keysList),
	BashComplete: func(c *cli.Context) {
		commandoptions.CompleteFlags(commandoptions.CommandFlags(flagsList, keysList))
	},
}

func flagsList() []cli.Flag {
	return []cli.Flag{
		cli.StringFlag{
			Name:  "server-id",
			Usage: "[optional; required if `server-name` or `stdin` isn't provided] The server ID of the attachment.",
		},
		cli.StringFlag{
			Name:  "server-name",
			Usage: "[optional; required if `server-id` or `stdin` isn't provided] The server name of the attachment.",
		},
		cli.StringFlag{
			Name:  "stdin",
			Usage: "[optional; required if `server-id` or `server-name` isn't provided] The field being piped into STDIN. Valid values are: server-id",
		},
	}
}

var keysList = []string{"ID", "Device", "VolumeID", "ServerID"}

type paramsList struct {
	serverID string
}

type commandList handler.Command

func actionList(c *cli.Context) {
	command := &commandList{
		Ctx: &handler.Context{
			CLIContext: c,
		},
	}
	handler.Handle(command)
}

func (command *commandList) Context() *handler.Context {
	return command.Ctx
}

func (command *commandList) Keys() []string {
	return keysList
}

func (command *commandList) ServiceClientType() string {
	return serviceClientType
}

func (command *commandList) HandleFlags(resource *handler.Resource) error {
	resource.Params = &paramsList{}
	return nil
}

func (command *commandList) HandlePipe(resource *handler.Resource, item string) error {
	resource.Params.(*paramsList).serverID = item
	return nil
}

func (command *commandList) HandleSingle(resource *handler.Resource) error {
	ctx := command.Ctx
	var serverID string
	if ctx.CLIContext.IsSet("server-id") {
		if ctx.CLIContext.IsSet("server-name") {
			return fmt.Errorf("Only one of either --server-id or --server-name may be provided.")
		}
		serverID = ctx.CLIContext.String("server-id")
	} else if ctx.CLIContext.IsSet("server-name") {
		name := ctx.CLIContext.String("server-name")
		id, err := osServers.IDFromName(ctx.ServiceClient, name)
		if err != nil {
			return fmt.Errorf("Error converting name [%s] to ID: %s", name, err)
		}
		serverID = id
	} else {
		return output.ErrMissingFlag{Msg: "One of either --server-id or --server-name must be provided."}
	}

	resource.Params.(*paramsList).serverID = serverID
	return nil
}

func (command *commandList) Execute(resource *handler.Resource) {
	params := resource.Params.(*paramsList)
	err := osVolumeAttach.List(command.Ctx.ServiceClient, params.serverID).EachPage(func(page pagination.Page) (bool, error) {
		volumeAttachments, err := osVolumeAttach.ExtractVolumeAttachments(page)
		if err != nil {
			return false, err
		}
		result := make([]map[string]interface{}, len(volumeAttachments))
		for j, volumeAttachment := range volumeAttachments {
			result[j] = structs.Map(volumeAttachment)
		}
		resource.Result = result
		return false, nil
	})
	if err != nil {
		resource.Err = err
		return
	}
}

func (command *commandList) StdinField() string {
	return "server-id"
}
<|endoftext|>


<commit_before>package gogobosh

import (
	"fmt"
	"time"
	"strings"
	"encoding/json"
)

func (repo BoshDirectorRepository) FetchVMsStatus(deploymentName string) (vmsStatuses []VMStatus, apiResponse ApiResponse) {
	var taskStatus TaskStatus

	/*
	* Two API calls
	* 1. GET /deployments/%s/vms?format=full and be redirected to a /tasks/123
	* 2. Streaming GET on /tasks/123/output?type=result - each line is a VMStatus
	*/
	path := fmt.Sprintf("/deployments/%s/vms?format=full", deploymentName)
	apiResponse = repo.gateway.GetResource(repo.config.TargetURL+path, repo.config.Username, repo.config.Password, &taskStatus)
	if apiResponse.IsNotSuccessful() {
		return
	}

	for taskStatus.State != "done" {
		time.Sleep(1)
		taskStatus, apiResponse = repo.GetTaskStatus(taskStatus.ID)
		if apiResponse.IsNotSuccessful() {
			return
		}
	}

	path = fmt.Sprintf("/tasks/%d/output?type=result", taskStatus.ID)
	request, apiResponse := repo.gateway.NewRequest("GET", repo.config.TargetURL+path, repo.config.Username, repo.config.Password, nil)
	if apiResponse.IsNotSuccessful() {
		return
	}

	bytes, _, apiResponse := repo.gateway.PerformRequestForResponseBytes(request)
	if apiResponse.IsNotSuccessful() {
		return
	}

	if apiResponse.StatusCode > 203 {
		return
	}

	for _, vmStatusResponse := range strings.Split(string(bytes), "\n") {
		resource := VMStatusResponse{}
		err := json.Unmarshal([]byte(vmStatusResponse), &resource)
		if err != nil {
/*			apiResponse = NewApiResponseWithError("Invalid JSON response from server", err)*/
			return
		}

		vmsStatuses = append(vmsStatuses, resource.ToModel())
	}

	return
}

type VMStatusResponse struct {
	JobName string  `json:"job_name"`
	Index int   `json:"index"`
	JobState string `json:"job_state"`
	VMCid string    `json:"vm_cid"`
	AgentID string  `json:"agent_id"`
	IPs []string    `json:"ips"`
	DNSs []string   `json:"dns"`
	ResourcePool string     `json:"resource_pool"`
	ResurrectionPaused bool `json:"resurrection_paused"`
	Vitals vitalsResponse `json:"vitals"`
}

type vitalsResponse struct {
	Load []string            `json:"load"`
	CPU cpuResponse          `json:"cpu"`
	Memory percentKbResponse `json:"mem"`
	Swap percentKbResponse   `json:"swap"`
	Disk diskResponse        `json:"disk"`
}

type cpuResponse struct {
	User float64    `json:"user,string"`
	System float64  `json:"sys,string"`
	Wait float64    `json:"wait,string"`
}

type diskResponse struct {
	Persistent percentKbResponse `json:"persistent"`
}

type percentKbResponse struct {
	Percent float64 `json:"percent,string"`
	Kb int          `json:"kb,string"`
}

func (resource VMStatusResponse) ToModel() (status VMStatus) {
	status = VMStatus{}
	status.JobName  = resource.JobName
	status.Index    = resource.Index
	status.JobState = resource.JobState
	status.VMCid    = resource.VMCid
	status.AgentID  = resource.AgentID
	status.ResourcePool = resource.ResourcePool
	status.ResurrectionPaused = resource.ResurrectionPaused

	status.IPs = resource.IPs
	status.DNSs = resource.DNSs

	status.CPUUser = resource.Vitals.CPU.User
	status.CPUSys = resource.Vitals.CPU.System
	status.CPUWait = resource.Vitals.CPU.Wait
	status.MemoryPercent = resource.Vitals.Memory.Percent
	status.MemoryKb = resource.Vitals.Memory.Kb
	status.SwapPercent = resource.Vitals.Swap.Percent
	status.SwapKb = resource.Vitals.Swap.Kb
	status.DiskPersistentPercent = resource.Vitals.Disk.Persistent.Percent

	return
}
<commit_msg>ignore any lines in response that aren't a VMStatusResponse{}<commit_after>package gogobosh

import (
	"fmt"
	"time"
	"strings"
	"encoding/json"
)

func (repo BoshDirectorRepository) FetchVMsStatus(deploymentName string) (vmsStatuses []VMStatus, apiResponse ApiResponse) {
	var taskStatus TaskStatus

	/*
	* Two API calls
	* 1. GET /deployments/%s/vms?format=full and be redirected to a /tasks/123
	* 2. Streaming GET on /tasks/123/output?type=result - each line is a VMStatus
	*/
	path := fmt.Sprintf("/deployments/%s/vms?format=full", deploymentName)
	apiResponse = repo.gateway.GetResource(repo.config.TargetURL+path, repo.config.Username, repo.config.Password, &taskStatus)
	if apiResponse.IsNotSuccessful() {
		return
	}

	/* Progression should be: queued, progressing, done */
	/* TODO task might fail */
	for taskStatus.State != "done" {
		time.Sleep(1)
		taskStatus, apiResponse = repo.GetTaskStatus(taskStatus.ID)
		if apiResponse.IsNotSuccessful() {
			return
		}
	}

	path = fmt.Sprintf("/tasks/%d/output?type=result", taskStatus.ID)
	request, apiResponse := repo.gateway.NewRequest("GET", repo.config.TargetURL+path, repo.config.Username, repo.config.Password, nil)
	if apiResponse.IsNotSuccessful() {
		return
	}

	bytes, _, apiResponse := repo.gateway.PerformRequestForResponseBytes(request)
	if apiResponse.IsNotSuccessful() {
		return
	}

	if apiResponse.StatusCode > 203 {
		return
	}

	for _, vmStatusResponse := range strings.Split(string(bytes), "\n") {
		resource := VMStatusResponse{}
		err := json.Unmarshal([]byte(vmStatusResponse), &resource)
		if err == nil {
			vmsStatuses = append(vmsStatuses, resource.ToModel())
		}
	}

	return
}

type VMStatusResponse struct {
	JobName string  `json:"job_name"`
	Index int   `json:"index"`
	JobState string `json:"job_state"`
	VMCid string    `json:"vm_cid"`
	AgentID string  `json:"agent_id"`
	IPs []string    `json:"ips"`
	DNSs []string   `json:"dns"`
	ResourcePool string     `json:"resource_pool"`
	ResurrectionPaused bool `json:"resurrection_paused"`
	Vitals vitalsResponse `json:"vitals"`
}

type vitalsResponse struct {
	Load []string            `json:"load"`
	CPU cpuResponse          `json:"cpu"`
	Memory percentKbResponse `json:"mem"`
	Swap percentKbResponse   `json:"swap"`
	Disk diskResponse        `json:"disk"`
}

type cpuResponse struct {
	User float64    `json:"user,string"`
	System float64  `json:"sys,string"`
	Wait float64    `json:"wait,string"`
}

type diskResponse struct {
	Persistent percentKbResponse `json:"persistent"`
}

type percentKbResponse struct {
	Percent float64 `json:"percent,string"`
	Kb int          `json:"kb,string"`
}

func (resource VMStatusResponse) ToModel() (status VMStatus) {
	status = VMStatus{}
	status.JobName  = resource.JobName
	status.Index    = resource.Index
	status.JobState = resource.JobState
	status.VMCid    = resource.VMCid
	status.AgentID  = resource.AgentID
	status.ResourcePool = resource.ResourcePool
	status.ResurrectionPaused = resource.ResurrectionPaused

	status.IPs = resource.IPs
	status.DNSs = resource.DNSs

	status.CPUUser = resource.Vitals.CPU.User
	status.CPUSys = resource.Vitals.CPU.System
	status.CPUWait = resource.Vitals.CPU.Wait
	status.MemoryPercent = resource.Vitals.Memory.Percent
	status.MemoryKb = resource.Vitals.Memory.Kb
	status.SwapPercent = resource.Vitals.Swap.Percent
	status.SwapKb = resource.Vitals.Swap.Kb
	status.DiskPersistentPercent = resource.Vitals.Disk.Persistent.Percent

	return
}
<|endoftext|>