<commit_before>// +build linux

package systemd

import (
	"errors"
	"os"
	"path/filepath"
	"strings"
	"sync"

	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/cgroups/fs"
	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/sirupsen/logrus"
)

type legacyManager struct {
	mu      sync.Mutex
	cgroups *configs.Cgroup
	paths   map[string]string
}

func NewLegacyManager(cg *configs.Cgroup, paths map[string]string) cgroups.Manager {
	return &legacyManager{
		cgroups: cg,
		paths:   paths,
	}
}

type subsystem interface {
	// Name returns the name of the subsystem.
	Name() string
	// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
	GetStats(path string, stats *cgroups.Stats) error
	// Set the cgroup represented by cgroup.
	Set(path string, cgroup *configs.Cgroup) error
}

var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")

var legacySubsystems = []subsystem{
	&fs.CpusetGroup{},
	&fs.DevicesGroup{},
	&fs.MemoryGroup{},
	&fs.CpuGroup{},
	&fs.CpuacctGroup{},
	&fs.PidsGroup{},
	&fs.BlkioGroup{},
	&fs.HugetlbGroup{},
	&fs.PerfEventGroup{},
	&fs.FreezerGroup{},
	&fs.NetPrioGroup{},
	&fs.NetClsGroup{},
	&fs.NameGroup{GroupName: "name=systemd"},
}

func genV1ResourcesProperties(c *configs.Cgroup, conn *systemdDbus.Conn) ([]systemdDbus.Property, error) {
	var properties []systemdDbus.Property
	r := c.Resources

	deviceProperties, err := generateDeviceProperties(r.Devices)
	if err != nil {
		return nil, err
	}
	properties = append(properties, deviceProperties...)

	if r.Memory != 0 {
		properties = append(properties,
			newProp("MemoryLimit", uint64(r.Memory)))
	}

	if r.CpuShares != 0 {
		properties = append(properties,
			newProp("CPUShares", r.CpuShares))
	}

	addCpuQuota(conn, &properties, r.CpuQuota, r.CpuPeriod)

	if r.BlkioWeight != 0 {
		properties = append(properties,
			newProp("BlockIOWeight", uint64(r.BlkioWeight)))
	}

	if r.PidsLimit > 0 || r.PidsLimit == -1 {
		properties = append(properties,
			newProp("TasksAccounting", true),
			newProp("TasksMax", uint64(r.PidsLimit)))
	}

	return properties, nil
}

func (m *legacyManager) Apply(pid int) error {
	var (
		c          = m.cgroups
		unitName   = getUnitName(c)
		slice      = "system.slice"
		properties []systemdDbus.Property
	)

	if c.Resources.Unified != nil {
		return cgroups.ErrV1NoUnified
	}

	m.mu.Lock()
	defer m.mu.Unlock()
	if c.Paths != nil {
		paths := make(map[string]string)
		cgMap, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
		if err != nil {
			return err
		}
		// XXX(kolyshkin@): why this check is needed?
		for name, path := range c.Paths {
			if _, ok := cgMap[name]; ok {
				paths[name] = path
			}
		}
		m.paths = paths
		return cgroups.EnterPid(m.paths, pid)
	}

	if c.Parent != "" {
		slice = c.Parent
	}

	properties = append(properties, systemdDbus.PropDescription("libcontainer container "+c.Name))

	// if we create a slice, the parent is defined via a Wants=
	if strings.HasSuffix(unitName, ".slice") {
		properties = append(properties, systemdDbus.PropWants(slice))
	} else {
		// otherwise, we use Slice=
		properties = append(properties, systemdDbus.PropSlice(slice))
	}

	// only add pid if its valid, -1 is used w/ general slice creation.
	if pid != -1 {
		properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
	}

	// Check if we can delegate. This is only supported on systemd versions 218 and above.
	if !strings.HasSuffix(unitName, ".slice") {
		// Assume scopes always support delegation.
		properties = append(properties, newProp("Delegate", true))
	}

	// Always enable accounting, this gets us the same behaviour as the fs implementation,
	// plus the kernel has some problems with joining the memory cgroup at a later time.
	properties = append(properties,
		newProp("MemoryAccounting", true),
		newProp("CPUAccounting", true),
		newProp("BlockIOAccounting", true))

	// Assume DefaultDependencies= will always work (the check for it was previously broken.)
	properties = append(properties,
		newProp("DefaultDependencies", false))

	dbusConnection, err := getDbusConnection(false)
	if err != nil {
		return err
	}
	resourcesProperties, err := genV1ResourcesProperties(c, dbusConnection)
	if err != nil {
		return err
	}
	properties = append(properties, resourcesProperties...)
	properties = append(properties, c.SystemdProps...)

	// We have to set kernel memory here, as we can't change it once
	// processes have been attached to the cgroup.
	if c.Resources.KernelMemory != 0 {
		if err := enableKmem(c); err != nil {
			return err
		}
	}

	if err := startUnit(dbusConnection, unitName, properties); err != nil {
		return err
	}

	paths := make(map[string]string)
	for _, s := range legacySubsystems {
		subsystemPath, err := getSubsystemPath(m.cgroups, s.Name())
		if err != nil {
			// Even if it's `not found` error, we'll return err
			// because devices cgroup is hard requirement for
			// container security.
			if s.Name() == "devices" {
				return err
			}
			// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
			if cgroups.IsNotFound(err) {
				continue
			}
			return err
		}
		paths[s.Name()] = subsystemPath
	}
	m.paths = paths

	if err := m.joinCgroups(pid); err != nil {
		return err
	}

	return nil
}

func (m *legacyManager) Destroy() error {
	if m.cgroups.Paths != nil {
		return nil
	}
	m.mu.Lock()
	defer m.mu.Unlock()

	dbusConnection, err := getDbusConnection(false)
	if err != nil {
		return err
	}
	unitName := getUnitName(m.cgroups)

	stopErr := stopUnit(dbusConnection, unitName)
	// Both on success and on error, cleanup all the cgroups we are aware of.
	// Some of them were created directly by Apply() and are not managed by systemd.
	if err := cgroups.RemovePaths(m.paths); err != nil {
		return err
	}

	return stopErr
}

func (m *legacyManager) Path(subsys string) string {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.paths[subsys]
}

func (m *legacyManager) joinCgroups(pid int) error {
	for _, sys := range legacySubsystems {
		name := sys.Name()
		switch name {
		case "name=systemd":
			// let systemd handle this
		case "cpuset":
			if path, ok := m.paths[name]; ok {
				s := &fs.CpusetGroup{}
				if err := s.ApplyDir(path, m.cgroups, pid); err != nil {
					return err
				}
			}
		default:
			if path, ok := m.paths[name]; ok {
				if err := os.MkdirAll(path, 0755); err != nil {
					return err
				}
				if err := cgroups.WriteCgroupProc(path, pid); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) {
	mountpoint, err := cgroups.FindCgroupMountpoint("", subsystem)
	if err != nil {
		return "", err
	}

	initPath, err := cgroups.GetInitCgroup(subsystem)
	if err != nil {
		return "", err
	}
	// if pid 1 is systemd 226 or later, it will be in init.scope, not the root
	initPath = strings.TrimSuffix(filepath.Clean(initPath), "init.scope")

	slice := "system.slice"
	if c.Parent != "" {
		slice = c.Parent
	}

	slice, err = ExpandSlice(slice)
	if err != nil {
		return "", err
	}

	return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil
}

func (m *legacyManager) Freeze(state configs.FreezerState) error {
	path, ok := m.paths["freezer"]
	if !ok {
		return errSubsystemDoesNotExist
	}
	prevState := m.cgroups.Resources.Freezer
	m.cgroups.Resources.Freezer = state
	freezer := &fs.FreezerGroup{}
	if err := freezer.Set(path, m.cgroups); err != nil {
		m.cgroups.Resources.Freezer = prevState
		return err
	}
	return nil
}

func (m *legacyManager) GetPids() ([]int, error) {
	path, ok := m.paths["devices"]
	if !ok {
		return nil, errSubsystemDoesNotExist
	}
	return cgroups.GetPids(path)
}

func (m *legacyManager) GetAllPids() ([]int, error) {
	path, ok := m.paths["devices"]
	if !ok {
		return nil, errSubsystemDoesNotExist
	}
	return cgroups.GetAllPids(path)
}

func (m *legacyManager) GetStats() (*cgroups.Stats, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	stats := cgroups.NewStats()
	for _, sys := range legacySubsystems {
		path := m.paths[sys.Name()]
		if path == "" {
			continue
		}
		if err := sys.GetStats(path, stats); err != nil {
			return nil, err
		}
	}

	return stats, nil
}

func (m *legacyManager) Set(container *configs.Config) error {
	// If Paths are set, then we are just joining cgroups paths
	// and there is no need to set any values.
	if m.cgroups.Paths != nil {
		return nil
	}
	if container.Cgroups.Resources.Unified != nil {
		return cgroups.ErrV1NoUnified
	}
	dbusConnection, err := getDbusConnection(false)
	if err != nil {
		return err
	}
	properties, err := genV1ResourcesProperties(container.Cgroups, dbusConnection)
	if err != nil {
		return err
	}

	// We have to freeze the container while systemd sets the cgroup settings.
	// The reason for this is that systemd's application of DeviceAllow rules
	// is done disruptively, resulting in spurrious errors to common devices
	// (unlike our fs driver, they will happily write deny-all rules to running
	// containers). So we freeze the container to avoid them hitting the cgroup
	// error. But if the freezer cgroup isn't supported, we just warn about it.
	targetFreezerState := configs.Undefined
	if !m.cgroups.SkipDevices {
		// Figure out the current freezer state, so we can revert to it after we
		// temporarily freeze the container.
		targetFreezerState, err = m.GetFreezerState()
		if err != nil {
			return err
		}
		if targetFreezerState == configs.Undefined {
			targetFreezerState = configs.Thawed
		}

		if err := m.Freeze(configs.Frozen); err != nil {
			logrus.Infof("freeze container before SetUnitProperties failed: %v", err)
		}
	}

	if err := dbusConnection.SetUnitProperties(getUnitName(container.Cgroups), true, properties...); err != nil {
		_ = m.Freeze(targetFreezerState)
		return err
	}

	// Reset freezer state before we apply the configuration, to avoid clashing
	// with the freezer setting in the configuration.
	_ = m.Freeze(targetFreezerState)

	for _, sys := range legacySubsystems {
		// Get the subsystem path, but don't error out for not found cgroups.
		path, ok := m.paths[sys.Name()]
		if !ok {
			continue
		}
		if err := sys.Set(path, container.Cgroups); err != nil {
			return err
		}
	}

	return nil
}

func enableKmem(c *configs.Cgroup) error {
	path, err := getSubsystemPath(c, "memory")
	if err != nil && !cgroups.IsNotFound(err) {
		return err
	}

	if err := os.MkdirAll(path, 0755); err != nil {
		return err
	}
	// do not try to enable the kernel memory if we already have
	// tasks in the cgroup.
	content, err := fscommon.ReadFile(path, "tasks")
	if err != nil {
		return err
	}
	if len(content) > 0 {
		return nil
	}
	return fs.EnableKernelMemoryAccounting(path)
}

func (m *legacyManager) GetPaths() map[string]string {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.paths
}

func (m *legacyManager) GetCgroups() (*configs.Cgroup, error) {
	return m.cgroups, nil
}

func (m *legacyManager) GetFreezerState() (configs.FreezerState, error) {
	path, ok := m.paths["freezer"]
	if !ok {
		return configs.Undefined, nil
	}
	freezer := &fs.FreezerGroup{}
	return freezer.GetState(path)
}

func (m *legacyManager) Exists() bool {
	return cgroups.PathExists(m.Path("devices"))
}
<commit_msg>libct/cg/systemd/v1: fix err check in enableKmem<commit_after>// +build linux

package systemd

import (
	"errors"
	"os"
	"path/filepath"
	"strings"
	"sync"

	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/cgroups/fs"
	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/sirupsen/logrus"
)

type legacyManager struct {
	mu      sync.Mutex
	cgroups *configs.Cgroup
	paths   map[string]string
}

func NewLegacyManager(cg *configs.Cgroup, paths map[string]string) cgroups.Manager {
	return &legacyManager{
		cgroups: cg,
		paths:   paths,
	}
}

type subsystem interface {
	// Name returns the name of the subsystem.
	Name() string
	// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
	GetStats(path string, stats *cgroups.Stats) error
	// Set the cgroup represented by cgroup.
	Set(path string, cgroup *configs.Cgroup) error
}

var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")

var legacySubsystems = []subsystem{
	&fs.CpusetGroup{},
	&fs.DevicesGroup{},
	&fs.MemoryGroup{},
	&fs.CpuGroup{},
	&fs.CpuacctGroup{},
	&fs.PidsGroup{},
	&fs.BlkioGroup{},
	&fs.HugetlbGroup{},
	&fs.PerfEventGroup{},
	&fs.FreezerGroup{},
	&fs.NetPrioGroup{},
	&fs.NetClsGroup{},
	&fs.NameGroup{GroupName: "name=systemd"},
}

func genV1ResourcesProperties(c *configs.Cgroup, conn *systemdDbus.Conn) ([]systemdDbus.Property, error) {
	var properties []systemdDbus.Property
	r := c.Resources

	deviceProperties, err := generateDeviceProperties(r.Devices)
	if err != nil {
		return nil, err
	}
	properties = append(properties, deviceProperties...)

	if r.Memory != 0 {
		properties = append(properties,
			newProp("MemoryLimit", uint64(r.Memory)))
	}

	if r.CpuShares != 0 {
		properties = append(properties,
			newProp("CPUShares", r.CpuShares))
	}

	addCpuQuota(conn, &properties, r.CpuQuota, r.CpuPeriod)

	if r.BlkioWeight != 0 {
		properties = append(properties,
			newProp("BlockIOWeight", uint64(r.BlkioWeight)))
	}

	if r.PidsLimit > 0 || r.PidsLimit == -1 {
		properties = append(properties,
			newProp("TasksAccounting", true),
			newProp("TasksMax", uint64(r.PidsLimit)))
	}

	return properties, nil
}

func (m *legacyManager) Apply(pid int) error {
	var (
		c          = m.cgroups
		unitName   = getUnitName(c)
		slice      = "system.slice"
		properties []systemdDbus.Property
	)

	if c.Resources.Unified != nil {
		return cgroups.ErrV1NoUnified
	}

	m.mu.Lock()
	defer m.mu.Unlock()
	if c.Paths != nil {
		paths := make(map[string]string)
		cgMap, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
		if err != nil {
			return err
		}
		// XXX(kolyshkin@): why this check is needed?
		for name, path := range c.Paths {
			if _, ok := cgMap[name]; ok {
				paths[name] = path
			}
		}
		m.paths = paths
		return cgroups.EnterPid(m.paths, pid)
	}

	if c.Parent != "" {
		slice = c.Parent
	}

	properties = append(properties, systemdDbus.PropDescription("libcontainer container "+c.Name))

	// if we create a slice, the parent is defined via a Wants=
	if strings.HasSuffix(unitName, ".slice") {
		properties = append(properties, systemdDbus.PropWants(slice))
	} else {
		// otherwise, we use Slice=
		properties = append(properties, systemdDbus.PropSlice(slice))
	}

	// only add pid if its valid, -1 is used w/ general slice creation.
	if pid != -1 {
		properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
	}

	// Check if we can delegate. This is only supported on systemd versions 218 and above.
	if !strings.HasSuffix(unitName, ".slice") {
		// Assume scopes always support delegation.
		properties = append(properties, newProp("Delegate", true))
	}

	// Always enable accounting, this gets us the same behaviour as the fs implementation,
	// plus the kernel has some problems with joining the memory cgroup at a later time.
	properties = append(properties,
		newProp("MemoryAccounting", true),
		newProp("CPUAccounting", true),
		newProp("BlockIOAccounting", true))

	// Assume DefaultDependencies= will always work (the check for it was previously broken.)
	properties = append(properties,
		newProp("DefaultDependencies", false))

	dbusConnection, err := getDbusConnection(false)
	if err != nil {
		return err
	}
	resourcesProperties, err := genV1ResourcesProperties(c, dbusConnection)
	if err != nil {
		return err
	}
	properties = append(properties, resourcesProperties...)
	properties = append(properties, c.SystemdProps...)

	// We have to set kernel memory here, as we can't change it once
	// processes have been attached to the cgroup.
	if c.Resources.KernelMemory != 0 {
		if err := enableKmem(c); err != nil {
			return err
		}
	}

	if err := startUnit(dbusConnection, unitName, properties); err != nil {
		return err
	}

	paths := make(map[string]string)
	for _, s := range legacySubsystems {
		subsystemPath, err := getSubsystemPath(m.cgroups, s.Name())
		if err != nil {
			// Even if it's `not found` error, we'll return err
			// because devices cgroup is hard requirement for
			// container security.
			if s.Name() == "devices" {
				return err
			}
			// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
			if cgroups.IsNotFound(err) {
				continue
			}
			return err
		}
		paths[s.Name()] = subsystemPath
	}
	m.paths = paths

	if err := m.joinCgroups(pid); err != nil {
		return err
	}

	return nil
}

func (m *legacyManager) Destroy() error {
	if m.cgroups.Paths != nil {
		return nil
	}
	m.mu.Lock()
	defer m.mu.Unlock()

	dbusConnection, err := getDbusConnection(false)
	if err != nil {
		return err
	}
	unitName := getUnitName(m.cgroups)

	stopErr := stopUnit(dbusConnection, unitName)
	// Both on success and on error, cleanup all the cgroups we are aware of.
	// Some of them were created directly by Apply() and are not managed by systemd.
	if err := cgroups.RemovePaths(m.paths); err != nil {
		return err
	}

	return stopErr
}

func (m *legacyManager) Path(subsys string) string {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.paths[subsys]
}

func (m *legacyManager) joinCgroups(pid int) error {
	for _, sys := range legacySubsystems {
		name := sys.Name()
		switch name {
		case "name=systemd":
			// let systemd handle this
		case "cpuset":
			if path, ok := m.paths[name]; ok {
				s := &fs.CpusetGroup{}
				if err := s.ApplyDir(path, m.cgroups, pid); err != nil {
					return err
				}
			}
		default:
			if path, ok := m.paths[name]; ok {
				if err := os.MkdirAll(path, 0755); err != nil {
					return err
				}
				if err := cgroups.WriteCgroupProc(path, pid); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) {
	mountpoint, err := cgroups.FindCgroupMountpoint("", subsystem)
	if err != nil {
		return "", err
	}

	initPath, err := cgroups.GetInitCgroup(subsystem)
	if err != nil {
		return "", err
	}
	// if pid 1 is systemd 226 or later, it will be in init.scope, not the root
	initPath = strings.TrimSuffix(filepath.Clean(initPath), "init.scope")

	slice := "system.slice"
	if c.Parent != "" {
		slice = c.Parent
	}

	slice, err = ExpandSlice(slice)
	if err != nil {
		return "", err
	}

	return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil
}

func (m *legacyManager) Freeze(state configs.FreezerState) error {
	path, ok := m.paths["freezer"]
	if !ok {
		return errSubsystemDoesNotExist
	}
	prevState := m.cgroups.Resources.Freezer
	m.cgroups.Resources.Freezer = state
	freezer := &fs.FreezerGroup{}
	if err := freezer.Set(path, m.cgroups); err != nil {
		m.cgroups.Resources.Freezer = prevState
		return err
	}
	return nil
}

func (m *legacyManager) GetPids() ([]int, error) {
	path, ok := m.paths["devices"]
	if !ok {
		return nil, errSubsystemDoesNotExist
	}
	return cgroups.GetPids(path)
}

func (m *legacyManager) GetAllPids() ([]int, error) {
	path, ok := m.paths["devices"]
	if !ok {
		return nil, errSubsystemDoesNotExist
	}
	return cgroups.GetAllPids(path)
}

func (m *legacyManager) GetStats() (*cgroups.Stats, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	stats := cgroups.NewStats()
	for _, sys := range legacySubsystems {
		path := m.paths[sys.Name()]
		if path == "" {
			continue
		}
		if err := sys.GetStats(path, stats); err != nil {
			return nil, err
		}
	}

	return stats, nil
}

func (m *legacyManager) Set(container *configs.Config) error {
	// If Paths are set, then we are just joining cgroups paths
	// and there is no need to set any values.
	if m.cgroups.Paths != nil {
		return nil
	}
	if container.Cgroups.Resources.Unified != nil {
		return cgroups.ErrV1NoUnified
	}
	dbusConnection, err := getDbusConnection(false)
	if err != nil {
		return err
	}
	properties, err := genV1ResourcesProperties(container.Cgroups, dbusConnection)
	if err != nil {
		return err
	}

	// We have to freeze the container while systemd sets the cgroup settings.
	// The reason for this is that systemd's application of DeviceAllow rules
	// is done disruptively, resulting in spurrious errors to common devices
	// (unlike our fs driver, they will happily write deny-all rules to running
	// containers). So we freeze the container to avoid them hitting the cgroup
	// error. But if the freezer cgroup isn't supported, we just warn about it.
	targetFreezerState := configs.Undefined
	if !m.cgroups.SkipDevices {
		// Figure out the current freezer state, so we can revert to it after we
		// temporarily freeze the container.
		targetFreezerState, err = m.GetFreezerState()
		if err != nil {
			return err
		}
		if targetFreezerState == configs.Undefined {
			targetFreezerState = configs.Thawed
		}

		if err := m.Freeze(configs.Frozen); err != nil {
			logrus.Infof("freeze container before SetUnitProperties failed: %v", err)
		}
	}

	if err := dbusConnection.SetUnitProperties(getUnitName(container.Cgroups), true, properties...); err != nil {
		_ = m.Freeze(targetFreezerState)
		return err
	}

	// Reset freezer state before we apply the configuration, to avoid clashing
	// with the freezer setting in the configuration.
	_ = m.Freeze(targetFreezerState)

	for _, sys := range legacySubsystems {
		// Get the subsystem path, but don't error out for not found cgroups.
		path, ok := m.paths[sys.Name()]
		if !ok {
			continue
		}
		if err := sys.Set(path, container.Cgroups); err != nil {
			return err
		}
	}

	return nil
}

func enableKmem(c *configs.Cgroup) error {
	path, err := getSubsystemPath(c, "memory")
	if err != nil {
		if cgroups.IsNotFound(err) {
			return nil
		}
		return err
	}

	if err := os.MkdirAll(path, 0755); err != nil {
		return err
	}
	// do not try to enable the kernel memory if we already have
	// tasks in the cgroup.
	content, err := fscommon.ReadFile(path, "tasks")
	if err != nil {
		return err
	}
	if len(content) > 0 {
		return nil
	}
	return fs.EnableKernelMemoryAccounting(path)
}

func (m *legacyManager) GetPaths() map[string]string {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.paths
}

func (m *legacyManager) GetCgroups() (*configs.Cgroup, error) {
	return m.cgroups, nil
}

func (m *legacyManager) GetFreezerState() (configs.FreezerState, error) {
	path, ok := m.paths["freezer"]
	if !ok {
		return configs.Undefined, nil
	}
	freezer := &fs.FreezerGroup{}
	return freezer.GetState(path)
}

func (m *legacyManager) Exists() bool {
	return cgroups.PathExists(m.Path("devices"))
}
<|endoftext|>


<commit_before>package net

import (
	_ "crypto/sha512"
	"crypto/x509"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strings"

	"github.com/cloudfoundry/cli/cf/errors"
	. "github.com/cloudfoundry/cli/cf/i18n"
	"golang.org/x/net/websocket"
)

//go:generate counterfeiter . HttpClientInterface
type HttpClientInterface interface {
	RequestDumperInterface

	Do(*http.Request) (*http.Response, error)
	ExecuteCheckRedirect(req *http.Request, via []*http.Request) error
}

type client struct {
	*http.Client
	dumper RequestDumper
}

var NewHttpClient = func(tr *http.Transport, dumper RequestDumper) HttpClientInterface {
	client := client{
		&http.Client{
			Transport: tr,
		},
		dumper,
	}
	client.CheckRedirect = client.checkRedirect

	return &client
}

func (cl *client) ExecuteCheckRedirect(req *http.Request, via []*http.Request) error {
	return cl.CheckRedirect(req, via)
}

func (cl *client) checkRedirect(req *http.Request, via []*http.Request) error {
	if len(via) > 1 {
		return errors.New(T("stopped after 1 redirect"))
	}

	prevReq := via[len(via)-1]
	cl.copyHeaders(prevReq, req, getBaseDomain(req.URL.String()) == getBaseDomain(via[0].URL.String()))
	cl.dumper.DumpRequest(req)

	return nil
}

func (cl *client) copyHeaders(from *http.Request, to *http.Request, sameDomain bool) {
	for key, values := range from.Header {
		// do not copy POST-specific headers
		if key != "Content-Type" && key != "Content-Length" && !(!sameDomain && key == "Authorization") {
			to.Header.Set(key, strings.Join(values, ","))
		}
	}
}

func (cl *client) DumpRequest(req *http.Request) {
	cl.dumper.DumpRequest(req)
}

func (cl *client) DumpResponse(res *http.Response) {
	cl.dumper.DumpResponse(res)
}

func WrapNetworkErrors(host string, err error) error {
	var innerErr error
	switch typedErr := err.(type) {
	case *url.Error:
		innerErr = typedErr.Err
	case *websocket.DialError:
		innerErr = typedErr.Err
	}

	if innerErr != nil {
		switch typedInnerErr := innerErr.(type) {
		case x509.UnknownAuthorityError:
			return errors.NewInvalidSSLCert(host, T("unknown authority"))
		case x509.HostnameError:
			return errors.NewInvalidSSLCert(host, T("not valid for the requested host"))
		case x509.CertificateInvalidError:
			return errors.NewInvalidSSLCert(host, "")
		case *net.OpError:
			if typedInnerErr.Op == "dial" {
				return fmt.Errorf("%s: %s\n%s", T("Error performing request"), err.Error(), T("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
			}
		}
	}

	return fmt.Errorf("%s: %s", T("Error performing request"), err.Error())
}

func getBaseDomain(host string) string {
	hostUrl, _ := url.Parse(host)
	hostStrs := strings.Split(hostUrl.Host, ".")
	return hostStrs[len(hostStrs)-2] + "." + hostStrs[len(hostStrs)-1]
}
<commit_msg>Attempt to fix go vet issue<commit_after>package net

import (
	_ "crypto/sha512"
	"crypto/x509"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strings"

	"github.com/cloudfoundry/cli/cf/errors"
	. "github.com/cloudfoundry/cli/cf/i18n"
	"golang.org/x/net/websocket"
)

//go:generate counterfeiter . HttpClientInterface
type HttpClientInterface interface {
	RequestDumperInterface

	Do(*http.Request) (*http.Response, error)
	ExecuteCheckRedirect(req *http.Request, via []*http.Request) error
}

type client struct {
	*http.Client
	dumper RequestDumper
}

var NewHttpClient = func(tr *http.Transport, dumper RequestDumper) HttpClientInterface {
	c := client{
		&http.Client{
			Transport: tr,
		},
		dumper,
	}
	c.CheckRedirect = c.checkRedirect

	return &c
}

func (cl *client) ExecuteCheckRedirect(req *http.Request, via []*http.Request) error {
	return cl.CheckRedirect(req, via)
}

func (cl *client) checkRedirect(req *http.Request, via []*http.Request) error {
	if len(via) > 1 {
		return errors.New(T("stopped after 1 redirect"))
	}

	prevReq := via[len(via)-1]
	cl.copyHeaders(prevReq, req, getBaseDomain(req.URL.String()) == getBaseDomain(via[0].URL.String()))
	cl.dumper.DumpRequest(req)

	return nil
}

func (cl *client) copyHeaders(from *http.Request, to *http.Request, sameDomain bool) {
	for key, values := range from.Header {
		// do not copy POST-specific headers
		if key != "Content-Type" && key != "Content-Length" && !(!sameDomain && key == "Authorization") {
			to.Header.Set(key, strings.Join(values, ","))
		}
	}
}

func (cl *client) DumpRequest(req *http.Request) {
	cl.dumper.DumpRequest(req)
}

func (cl *client) DumpResponse(res *http.Response) {
	cl.dumper.DumpResponse(res)
}

func WrapNetworkErrors(host string, err error) error {
	var innerErr error
	switch typedErr := err.(type) {
	case *url.Error:
		innerErr = typedErr.Err
	case *websocket.DialError:
		innerErr = typedErr.Err
	}

	if innerErr != nil {
		switch typedInnerErr := innerErr.(type) {
		case x509.UnknownAuthorityError:
			return errors.NewInvalidSSLCert(host, T("unknown authority"))
		case x509.HostnameError:
			return errors.NewInvalidSSLCert(host, T("not valid for the requested host"))
		case x509.CertificateInvalidError:
			return errors.NewInvalidSSLCert(host, "")
		case *net.OpError:
			if typedInnerErr.Op == "dial" {
				return fmt.Errorf("%s: %s\n%s", T("Error performing request"), err.Error(), T("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
			}
		}
	}

	return fmt.Errorf("%s: %s", T("Error performing request"), err.Error())
}

func getBaseDomain(host string) string {
	hostUrl, _ := url.Parse(host)
	hostStrs := strings.Split(hostUrl.Host, ".")
	return hostStrs[len(hostStrs)-2] + "." + hostStrs[len(hostStrs)-1]
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "flag"
    "os"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
)

func main() {
    protectingTags := map[string]bool {"dont-kill": true, "production": true}
    killEnabled := flag.Bool("kill-enabled", false, "kill for real - default is dry run")
    probabilityString := flag.String("probability", "", "probability of a kill per ASG")

    flag.Parse()

    sess, err := session.NewSession()
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error - problem creating session", err)
        os.Exit(1)
    }

    region := aws.Config{Region: aws.String("ap-southeast-2")}

    if *probabilityString == "" {
        fmt.Fprintf(os.Stderr, "Error - probability string not set\n")
        os.Exit(3)
    }

    probability, err := parseProbability(*probabilityString)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error - problem with probability string:", *probabilityString, "\n")
        os.Exit(2)
    }

    killProbability, err := assertProbability(probability)
    if err != nil {
        fmt.Fprintf(os.Stderr, "probability value assert fail\n")
        os.Exit(4)
    }

    fmt.Printf("kill enabled: %t, kill probability: %.2f%%\n", *killEnabled, killProbability*100)
    //fmt.Println("killProbability:", killProbability)

    initKiller()

    asgs := collateASGs(&region, sess)
    instances := identifyInstancesToKill(asgs, killProbability, &protectingTags)
    killInstances(&region, sess, instances, *killEnabled)
}
<commit_msg>protecting tags on the command line<commit_after>package main

import (
    "fmt"
    "flag"
    "os"
    "reflect"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
)

func main() {
    var protectingTagsArray stringArrayFlags
    killEnabled := flag.Bool("kill-enabled", false, "kill for real - default is dry run")
    probabilityString := flag.String("probability", "", "probability of a kill per ASG")
    flag.Var(&protectingTagsArray, "protect-tag", "ASG tags")

    protectingTags := make(map[string]bool)

    flag.Parse()

    for _, tag := range protectingTagsArray {
        protectingTags[tag] = true
    }

    fmt.Println(protectingTagsArray)
    fmt.Println(protectingTags)

    sess, err := session.NewSession()
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error - problem creating session", err)
        os.Exit(1)
    }

    region := aws.Config{Region: aws.String("ap-southeast-2")}

    if *probabilityString == "" {
        fmt.Fprintf(os.Stderr, "Error - probability string not set\n")
        os.Exit(3)
    }

    probability, err := parseProbability(*probabilityString)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error - problem with probability string:", *probabilityString, "\n")
        os.Exit(2)
    }

    killProbability, err := assertProbability(probability)
    if err != nil {
        fmt.Fprintf(os.Stderr, "probability value assert fail\n")
        os.Exit(4)
    }

    fmt.Printf("kill enabled: %t, kill probability: %.2f%%, protecting tags: %v\n", *killEnabled, killProbability*100, reflect.ValueOf(protectingTags).MapKeys())
    //fmt.Println("killProbability:", killProbability)

    initKiller()

    asgs := collateASGs(&region, sess)
    instances := identifyInstancesToKill(asgs, killProbability, &protectingTags)
    killInstances(&region, sess, instances, *killEnabled)
}

type stringArrayFlags []string

func (i *stringArrayFlags) String() string {
    return "my string representation"
}

func (i *stringArrayFlags) Set(value string) error {
    *i = append(*i, value)
    return nil
}<|endoftext|>


<commit_before>package libvirt

import (
	"encoding/xml"
	"fmt"
	libvirt "github.com/dmacvicar/libvirt-go"
	"github.com/hashicorp/terraform/helper/schema"
	"log"
	"time"
)

func resourceLibvirtDomain() *schema.Resource {
	return &schema.Resource{
		Create: resourceLibvirtDomainCreate,
		Read:   resourceLibvirtDomainRead,
		Delete: resourceLibvirtDomainDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"vcpu": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  1,
				ForceNew: true,
			},
			"memory": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  512,
				ForceNew: true,
			},
			"disk": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Required: false,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: diskCommonSchema(),
				},
			},
			"network_interface": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Required: false,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: networkInterfaceCommonSchema(),
				},
			},
		},
	}
}

func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	disksCount := d.Get("disk.#").(int)
	disks := make([]defDisk, 0, disksCount)
	for i := 0; i < disksCount; i++ {
		prefix := fmt.Sprintf("disk.%d", i)
		disk := newDefDisk()
		disk.Target.Dev = fmt.Sprintf("vd%s", DiskLetterForIndex(i))

		volumeKey := d.Get(prefix + ".volume_id").(string)
		diskVolume, err := virConn.LookupStorageVolByKey(volumeKey)
		if err != nil {
			return fmt.Errorf("Can't retrieve volume %s", volumeKey)
		}
		diskVolumeName, err := diskVolume.GetName()
		if err != nil {
			return fmt.Errorf("Error retrieving volume name: %s", err)
		}
		diskPool, err := diskVolume.LookupPoolByVolume()
		if err != nil {
			return fmt.Errorf("Error retrieving pool for volume: %s", err)
		}
		diskPoolName, err := diskPool.GetName()
		if err != nil {
			return fmt.Errorf("Error retrieving pool name: %s", err)
		}

		disk.Source.Volume = diskVolumeName
		disk.Source.Pool = diskPoolName

		disks = append(disks, disk)
	}

	netIfacesCount := d.Get("network_interface.#").(int)
	netIfaces := make([]defNetworkInterface, 0, netIfacesCount)
	for i := 0; i < netIfacesCount; i++ {
		prefix := fmt.Sprintf("network_interface.%d", i)
		netIface := newDefNetworkInterface()

		if mac, ok := d.GetOk(prefix + ".mac"); ok {
			netIface.Mac.Address = mac.(string)
		} else {
			var err error
			netIface.Mac.Address, err = RandomMACAddress()
			if err != nil {
				return fmt.Errorf("Error generating mac address: %s", err)
			}
		}

		// this is not passes to libvirt, but used by waitForAddress
		if waitForLease, ok := d.GetOk(prefix + ".wait_for_lease"); ok {
			netIface.waitForLease = waitForLease.(bool)
		}

		if network, ok := d.GetOk(prefix + ".network"); ok {
			netIface.Source.Network = network.(string)
		}
		netIfaces = append(netIfaces, netIface)
	}

	domainDef := newDomainDef()
	if name, ok := d.GetOk("name"); ok {
		domainDef.Name = name.(string)
	}

	domainDef.Memory.Amount = d.Get("memory").(int)
	domainDef.VCpu.Amount = d.Get("vcpu").(int)
	domainDef.Devices.Disks = disks
	domainDef.Devices.NetworkInterfaces = netIfaces

	connectURI, err := virConn.GetURI()
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt connection URI: %s", err)
	}
	log.Printf("[INFO] Creating libvirt domain at %s", connectURI)

	data, err := xml.Marshal(domainDef)
	if err != nil {
		return fmt.Errorf("Error serializing libvirt domain: %s", err)
	}

	domain, err := virConn.DomainCreateXML(string(data), libvirt.VIR_DOMAIN_NONE)
	if err != nil {
		return fmt.Errorf("Error crearing libvirt domain: %s", err)
	}
	defer domain.Free()

	id, err := domain.GetUUIDString()
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain id: %s", err)
	}
	d.SetId(id)

	log.Printf("[INFO] Domain ID: %s", d.Id())

	err = waitForDomainUp(domain)
	if err != nil {
		return fmt.Errorf("Error waiting for domain to reach RUNNING state: %s", err)
	}

	err = waitForNetworkAddresses(netIfaces, domain)
	if err != nil {
		return err
	}

	return resourceLibvirtDomainRead(d, meta)
}

func resourceLibvirtDomainRead(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	domain, err := virConn.LookupByUUIDString(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain: %s", err)
	}
	defer domain.Free()

	xmlDesc, err := domain.GetXMLDesc(0)
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain XML description: %s", err)
	}

	domainDef := newDomainDef()
	err = xml.Unmarshal([]byte(xmlDesc), &domainDef)
	if err != nil {
		return fmt.Errorf("Error reading libvirt domain XML description: %s", err)
	}

	d.Set("name", domainDef.Name)
	d.Set("vpu", domainDef.VCpu)
	d.Set("memory", domainDef.Memory)

	disks := make([]map[string]interface{}, 0)
	for _, diskDef := range domainDef.Devices.Disks {
		virPool, err := virConn.LookupStoragePoolByName(diskDef.Source.Pool)
		if err != nil {
			return fmt.Errorf("Error retrieving pool for disk: %s", err)
		}
		defer virPool.Free()

		virVol, err := virPool.LookupStorageVolByName(diskDef.Source.Volume)
		if err != nil {
			return fmt.Errorf("Error retrieving volume for disk: %s", err)
		}
		defer virVol.Free()

		virVolKey, err := virVol.GetKey()
		if err != nil {
			return fmt.Errorf("Error retrieving volume ke for disk: %s", err)
		}

		disk := map[string]interface{}{
			"volume_id": virVolKey,
		}
		disks = append(disks, disk)
	}
	d.Set("disks", disks)

	// look interfaces with addresses
	ifacesWithAddr, err := domain.ListAllInterfaceAddresses(libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE)
	if err != nil {
		return fmt.Errorf("Error retrieving interface addresses: %s", err)
	}

	netIfaces := make([]map[string]interface{}, 0)
	for i, networkInterfaceDef := range domainDef.Devices.NetworkInterfaces {
		// we need it to read old values
		prefix := fmt.Sprintf("network_interface.%d", i)

		if networkInterfaceDef.Type != "network" {
			log.Printf("[DEBUG] ignoring interface of type '%s'", networkInterfaceDef.Type)
			continue
		}

		netIface := map[string]interface{}{
			"network": networkInterfaceDef.Source.Network,
			"mac":     networkInterfaceDef.Mac.Address,
		}

		netIfaceAddrs := make([]map[string]interface{}, 0)
		// look for an ip address and try to match it with the mac address
		// not sure if using the target device name is a better idea here
		for _, ifaceWithAddr := range ifacesWithAddr {
			if ifaceWithAddr.Hwaddr == networkInterfaceDef.Mac.Address {
				for _, addr := range ifaceWithAddr.Addrs {
					netIfaceAddr := map[string]interface{}{
						"type": func() string {
							switch addr.Type {
							case libvirt.VIR_IP_ADDR_TYPE_IPV4:
								return "ipv4"
							case libvirt.VIR_IP_ADDR_TYPE_IPV6:
								return "ipv6"
							default:
								return "other"
							}
						}(),
						"address": addr.Addr,
						"prefix":  addr.Prefix,
					}
					netIfaceAddrs = append(netIfaceAddrs, netIfaceAddr)
				}
			}
		}

		log.Printf("[DEBUG] %d addresses for %s\n", len(netIfaceAddrs), networkInterfaceDef.Mac.Address)
		netIface["address"] = netIfaceAddrs

		// pass on old wait_for_lease value
		if waitForLease, ok := d.GetOk(prefix + ".wait_for_lease"); ok {
			netIface["wait_for_lease"] = waitForLease
		}

		netIfaces = append(netIfaces, netIface)
	}
	d.Set("network_interface", netIfaces)

	if len(ifacesWithAddr) > 0 {
		d.SetConnInfo(map[string]string{
			"type": "ssh",
			"host": ifacesWithAddr[0].Addrs[0].Addr,
		})
	}
	return nil
}

func resourceLibvirtDomainDelete(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	domain, err := virConn.LookupByUUIDString(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain: %s", err)
	}
	defer domain.Free()

	if err := domain.Destroy(); err != nil {
		return fmt.Errorf("Couldn't destroy libvirt domain: %s", err)
	}

	err = waitForDomainDestroyed(virConn, d.Id())
	if err != nil {
		return fmt.Errorf("Error waiting for domain to be destroyed: %s", err)
	}

	return nil
}

// wait for domain to be up and timeout after 5 minutes.
func waitForDomainUp(domain libvirt.VirDomain) error {
	start := time.Now()
	for {
		state, err := domain.GetState()
		if err != nil {
			return err
		}

		running := true
		if state[0] != libvirt.VIR_DOMAIN_RUNNING {
			running = false
		}

		if running {
			return nil
		}
		time.Sleep(1 * time.Second)
		if time.Since(start) > 5*time.Minute {
			return fmt.Errorf("Domain didn't switch to state RUNNING in 5 minutes")
		}
	}
}

// wait for domain to be up and timeout after 5 minutes.
func waitForDomainDestroyed(virConn *libvirt.VirConnection, uuid string) error {
	start := time.Now()
	for {
		log.Printf("Waiting for domain %s to be destroyed", uuid)
		_, err := virConn.LookupByUUIDString(uuid)
		if err.(libvirt.VirError).Code == libvirt.VIR_ERR_NO_DOMAIN {
			return nil
		}

		time.Sleep(1 * time.Second)
		if time.Since(start) > 5*time.Minute {
			return fmt.Errorf("Domain is still there after 5 minutes")
		}
	}
}

func waitForNetworkAddresses(ifaces []defNetworkInterface, domain libvirt.VirDomain) error {
	log.Printf("[DEBUG] waiting for network addresses.\n")
	// wait for network interfaces with 'wait_for_lease' to get an address
	for _, iface := range(ifaces) {
		if !iface.waitForLease {
			continue
		}

		if iface.Mac.Address == "" {
			log.Printf("[DEBUG] Can't wait without a mac address.\n")
			// we can't get the ip without a mac address
			continue
		}

		// loop until address appear, with timeout
		start := time.Now()
	waitLoop:
		for {
			log.Printf("[DEBUG] waiting for network address for interface with hwaddr: '%s'\n", iface.Mac.Address)
			ifacesWithAddr, err := domain.ListAllInterfaceAddresses(libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE)
			if err != nil {
				return fmt.Errorf("Error retrieving interface addresses: %s", err)
			}

			for _, ifaceWithAddr := range ifacesWithAddr {
				// found
				if iface.Mac.Address == ifaceWithAddr.Hwaddr {
					break waitLoop
				}
			}

			time.Sleep(1 * time.Second)
			if time.Since(start) > 5*time.Minute {
				return fmt.Errorf("Timeout waiting for interface addresses")
			}
		}
	}

	return nil
}
<commit_msg>go fmt<commit_after>package libvirt

import (
	"encoding/xml"
	"fmt"
	libvirt "github.com/dmacvicar/libvirt-go"
	"github.com/hashicorp/terraform/helper/schema"
	"log"
	"time"
)

func resourceLibvirtDomain() *schema.Resource {
	return &schema.Resource{
		Create: resourceLibvirtDomainCreate,
		Read:   resourceLibvirtDomainRead,
		Delete: resourceLibvirtDomainDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"vcpu": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  1,
				ForceNew: true,
			},
			"memory": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  512,
				ForceNew: true,
			},
			"disk": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Required: false,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: diskCommonSchema(),
				},
			},
			"network_interface": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Required: false,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: networkInterfaceCommonSchema(),
				},
			},
		},
	}
}

func resourceLibvirtDomainCreate(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	disksCount := d.Get("disk.#").(int)
	disks := make([]defDisk, 0, disksCount)
	for i := 0; i < disksCount; i++ {
		prefix := fmt.Sprintf("disk.%d", i)
		disk := newDefDisk()
		disk.Target.Dev = fmt.Sprintf("vd%s", DiskLetterForIndex(i))

		volumeKey := d.Get(prefix + ".volume_id").(string)
		diskVolume, err := virConn.LookupStorageVolByKey(volumeKey)
		if err != nil {
			return fmt.Errorf("Can't retrieve volume %s", volumeKey)
		}
		diskVolumeName, err := diskVolume.GetName()
		if err != nil {
			return fmt.Errorf("Error retrieving volume name: %s", err)
		}
		diskPool, err := diskVolume.LookupPoolByVolume()
		if err != nil {
			return fmt.Errorf("Error retrieving pool for volume: %s", err)
		}
		diskPoolName, err := diskPool.GetName()
		if err != nil {
			return fmt.Errorf("Error retrieving pool name: %s", err)
		}

		disk.Source.Volume = diskVolumeName
		disk.Source.Pool = diskPoolName

		disks = append(disks, disk)
	}

	netIfacesCount := d.Get("network_interface.#").(int)
	netIfaces := make([]defNetworkInterface, 0, netIfacesCount)
	for i := 0; i < netIfacesCount; i++ {
		prefix := fmt.Sprintf("network_interface.%d", i)
		netIface := newDefNetworkInterface()

		if mac, ok := d.GetOk(prefix + ".mac"); ok {
			netIface.Mac.Address = mac.(string)
		} else {
			var err error
			netIface.Mac.Address, err = RandomMACAddress()
			if err != nil {
				return fmt.Errorf("Error generating mac address: %s", err)
			}
		}

		// this is not passes to libvirt, but used by waitForAddress
		if waitForLease, ok := d.GetOk(prefix + ".wait_for_lease"); ok {
			netIface.waitForLease = waitForLease.(bool)
		}

		if network, ok := d.GetOk(prefix + ".network"); ok {
			netIface.Source.Network = network.(string)
		}
		netIfaces = append(netIfaces, netIface)
	}

	domainDef := newDomainDef()
	if name, ok := d.GetOk("name"); ok {
		domainDef.Name = name.(string)
	}

	domainDef.Memory.Amount = d.Get("memory").(int)
	domainDef.VCpu.Amount = d.Get("vcpu").(int)
	domainDef.Devices.Disks = disks
	domainDef.Devices.NetworkInterfaces = netIfaces

	connectURI, err := virConn.GetURI()
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt connection URI: %s", err)
	}
	log.Printf("[INFO] Creating libvirt domain at %s", connectURI)

	data, err := xml.Marshal(domainDef)
	if err != nil {
		return fmt.Errorf("Error serializing libvirt domain: %s", err)
	}

	domain, err := virConn.DomainCreateXML(string(data), libvirt.VIR_DOMAIN_NONE)
	if err != nil {
		return fmt.Errorf("Error crearing libvirt domain: %s", err)
	}
	defer domain.Free()

	id, err := domain.GetUUIDString()
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain id: %s", err)
	}
	d.SetId(id)

	log.Printf("[INFO] Domain ID: %s", d.Id())

	err = waitForDomainUp(domain)
	if err != nil {
		return fmt.Errorf("Error waiting for domain to reach RUNNING state: %s", err)
	}

	err = waitForNetworkAddresses(netIfaces, domain)
	if err != nil {
		return err
	}

	return resourceLibvirtDomainRead(d, meta)
}

func resourceLibvirtDomainRead(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	domain, err := virConn.LookupByUUIDString(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain: %s", err)
	}
	defer domain.Free()

	xmlDesc, err := domain.GetXMLDesc(0)
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain XML description: %s", err)
	}

	domainDef := newDomainDef()
	err = xml.Unmarshal([]byte(xmlDesc), &domainDef)
	if err != nil {
		return fmt.Errorf("Error reading libvirt domain XML description: %s", err)
	}

	d.Set("name", domainDef.Name)
	d.Set("vpu", domainDef.VCpu)
	d.Set("memory", domainDef.Memory)

	disks := make([]map[string]interface{}, 0)
	for _, diskDef := range domainDef.Devices.Disks {
		virPool, err := virConn.LookupStoragePoolByName(diskDef.Source.Pool)
		if err != nil {
			return fmt.Errorf("Error retrieving pool for disk: %s", err)
		}
		defer virPool.Free()

		virVol, err := virPool.LookupStorageVolByName(diskDef.Source.Volume)
		if err != nil {
			return fmt.Errorf("Error retrieving volume for disk: %s", err)
		}
		defer virVol.Free()

		virVolKey, err := virVol.GetKey()
		if err != nil {
			return fmt.Errorf("Error retrieving volume ke for disk: %s", err)
		}

		disk := map[string]interface{}{
			"volume_id": virVolKey,
		}
		disks = append(disks, disk)
	}
	d.Set("disks", disks)

	// look interfaces with addresses
	ifacesWithAddr, err := domain.ListAllInterfaceAddresses(libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE)
	if err != nil {
		return fmt.Errorf("Error retrieving interface addresses: %s", err)
	}

	netIfaces := make([]map[string]interface{}, 0)
	for i, networkInterfaceDef := range domainDef.Devices.NetworkInterfaces {
		// we need it to read old values
		prefix := fmt.Sprintf("network_interface.%d", i)

		if networkInterfaceDef.Type != "network" {
			log.Printf("[DEBUG] ignoring interface of type '%s'", networkInterfaceDef.Type)
			continue
		}

		netIface := map[string]interface{}{
			"network": networkInterfaceDef.Source.Network,
			"mac":     networkInterfaceDef.Mac.Address,
		}

		netIfaceAddrs := make([]map[string]interface{}, 0)
		// look for an ip address and try to match it with the mac address
		// not sure if using the target device name is a better idea here
		for _, ifaceWithAddr := range ifacesWithAddr {
			if ifaceWithAddr.Hwaddr == networkInterfaceDef.Mac.Address {
				for _, addr := range ifaceWithAddr.Addrs {
					netIfaceAddr := map[string]interface{}{
						"type": func() string {
							switch addr.Type {
							case libvirt.VIR_IP_ADDR_TYPE_IPV4:
								return "ipv4"
							case libvirt.VIR_IP_ADDR_TYPE_IPV6:
								return "ipv6"
							default:
								return "other"
							}
						}(),
						"address": addr.Addr,
						"prefix":  addr.Prefix,
					}
					netIfaceAddrs = append(netIfaceAddrs, netIfaceAddr)
				}
			}
		}

		log.Printf("[DEBUG] %d addresses for %s\n", len(netIfaceAddrs), networkInterfaceDef.Mac.Address)
		netIface["address"] = netIfaceAddrs

		// pass on old wait_for_lease value
		if waitForLease, ok := d.GetOk(prefix + ".wait_for_lease"); ok {
			netIface["wait_for_lease"] = waitForLease
		}

		netIfaces = append(netIfaces, netIface)
	}
	d.Set("network_interface", netIfaces)

	if len(ifacesWithAddr) > 0 {
		d.SetConnInfo(map[string]string{
			"type": "ssh",
			"host": ifacesWithAddr[0].Addrs[0].Addr,
		})
	}
	return nil
}

func resourceLibvirtDomainDelete(d *schema.ResourceData, meta interface{}) error {
	virConn := meta.(*Client).libvirt
	if virConn == nil {
		return fmt.Errorf("The libvirt connection was nil.")
	}

	domain, err := virConn.LookupByUUIDString(d.Id())
	if err != nil {
		return fmt.Errorf("Error retrieving libvirt domain: %s", err)
	}
	defer domain.Free()

	if err := domain.Destroy(); err != nil {
		return fmt.Errorf("Couldn't destroy libvirt domain: %s", err)
	}

	err = waitForDomainDestroyed(virConn, d.Id())
	if err != nil {
		return fmt.Errorf("Error waiting for domain to be destroyed: %s", err)
	}

	return nil
}

// wait for domain to be up and timeout after 5 minutes.
func waitForDomainUp(domain libvirt.VirDomain) error {
	start := time.Now()
	for {
		state, err := domain.GetState()
		if err != nil {
			return err
		}

		running := true
		if state[0] != libvirt.VIR_DOMAIN_RUNNING {
			running = false
		}

		if running {
			return nil
		}
		time.Sleep(1 * time.Second)
		if time.Since(start) > 5*time.Minute {
			return fmt.Errorf("Domain didn't switch to state RUNNING in 5 minutes")
		}
	}
}

// wait for domain to be up and timeout after 5 minutes.
func waitForDomainDestroyed(virConn *libvirt.VirConnection, uuid string) error {
	start := time.Now()
	for {
		log.Printf("Waiting for domain %s to be destroyed", uuid)
		_, err := virConn.LookupByUUIDString(uuid)
		if err.(libvirt.VirError).Code == libvirt.VIR_ERR_NO_DOMAIN {
			return nil
		}

		time.Sleep(1 * time.Second)
		if time.Since(start) > 5*time.Minute {
			return fmt.Errorf("Domain is still there after 5 minutes")
		}
	}
}

func waitForNetworkAddresses(ifaces []defNetworkInterface, domain libvirt.VirDomain) error {
	log.Printf("[DEBUG] waiting for network addresses.\n")
	// wait for network interfaces with 'wait_for_lease' to get an address
	for _, iface := range ifaces {
		if !iface.waitForLease {
			continue
		}

		if iface.Mac.Address == "" {
			log.Printf("[DEBUG] Can't wait without a mac address.\n")
			// we can't get the ip without a mac address
			continue
		}

		// loop until address appear, with timeout
		start := time.Now()
	waitLoop:
		for {
			log.Printf("[DEBUG] waiting for network address for interface with hwaddr: '%s'\n", iface.Mac.Address)
			ifacesWithAddr, err := domain.ListAllInterfaceAddresses(libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE)
			if err != nil {
				return fmt.Errorf("Error retrieving interface addresses: %s", err)
			}

			for _, ifaceWithAddr := range ifacesWithAddr {
				// found
				if iface.Mac.Address == ifaceWithAddr.Hwaddr {
					break waitLoop
				}
			}

			time.Sleep(1 * time.Second)
			if time.Since(start) > 5*time.Minute {
				return fmt.Errorf("Timeout waiting for interface addresses")
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package influxql_test

import (
	"strings"
	"testing"

	"github.com/influxdb/influxdb/influxql"
)

// Ensure a value's data type can be retrieved.
func TestInspectDataType(t *testing.T) {
	for i, tt := range []struct {
		v   interface{}
		typ influxql.DataType
	}{
		{float64(100), influxql.Number},
	} {
		if typ := influxql.InspectDataType(tt.v); tt.typ != typ {
			t.Errorf("%d. %v (%s): unexpected type: %s", i, tt.v, tt.typ, typ)
			continue
		}
	}
}

// Ensure the SELECT statement can extract substatements.
func TestSelectStatement_Substatement(t *testing.T) {
	var tests = []struct {
		stmt string
		expr *influxql.VarRef
		sub  string
		err  string
	}{
		// 0. Single series
		{
			stmt: `SELECT value FROM myseries WHERE value > 1`,
			expr: &influxql.VarRef{Val: "value"},
			sub:  `SELECT value FROM myseries WHERE value > 1.000`,
		},

		// 1. Simple join
		{
			stmt: `SELECT sum(aa.value) + sum(bb.value) FROM aa JOIN bb`,
			expr: &influxql.VarRef{Val: "aa.value"},
			sub:  `SELECT aa.value FROM aa`,
		},

		// 2. Simple merge
		{
			stmt: `SELECT sum(aa.value) + sum(bb.value) FROM aa MERGE bb`,
			expr: &influxql.VarRef{Val: "bb.value"},
			sub:  `SELECT bb.value FROM bb`,
		},

		// 3. Join with condition
		{
			stmt: `SELECT sum(aa.value) + sum(bb.value) FROM aa JOIN bb WHERE aa.host = "servera" AND bb.host = "serverb"`,
			expr: &influxql.VarRef{Val: "bb.value"},
			sub:  `SELECT bb.value FROM bb WHERE bb.host = "serverb"`,
		},

		// 4. Join with complex condition
		{
			stmt: `SELECT sum(aa.value) + sum(bb.value) FROM aa JOIN bb WHERE aa.host = "servera" AND (bb.host = "serverb" OR bb.host = "serverc") AND 1 = 2`,
			expr: &influxql.VarRef{Val: "bb.value"},
			sub:  `SELECT bb.value FROM bb WHERE (bb.host = "serverb" OR bb.host = "serverc") AND 1.000 = 2.000`,
		},
	}

	for i, tt := range tests {
		// Parse statement.
		stmt, err := influxql.NewParser(strings.NewReader(tt.stmt)).ParseStatement()
		if err != nil {
			t.Fatalf("invalid statement: %q: %s", tt.stmt, err)
		}

		// Extract substatement.
		sub, err := stmt.(*influxql.SelectStatement).Substatement(tt.expr)
		if err != nil {
			t.Errorf("%d. %q: unexpected error: %s", i, tt.stmt, err)
			continue
		}
		if substr := sub.String(); tt.sub != substr {
			t.Errorf("%d. %q: unexpected substatement:\n\nexp=%s\n\ngot=%s\n\n", i, tt.stmt, tt.sub, substr)
			continue
		}
	}
}

// Ensure an expression can be folded.
func TestFold(t *testing.T) {
	for i, tt := range []struct {
		in  string
		out string
	}{
		// Number literals.
		{`1 + 2`, `3.000`},
		{`(foo*2) + (4/2) + (3 * 5) - 0.5`, `(foo * 2.000) + 16.500`},
		{`foo(bar(2 + 3), 4)`, `foo(bar(5.000), 4.000)`},
		{`4 / 0`, `0.000`},
		{`4 = 4`, `true`},
		{`4 != 4`, `false`},
		{`6 > 4`, `true`},
		{`4 >= 4`, `true`},
		{`4 < 6`, `true`},
		{`4 <= 4`, `true`},
		{`4 AND 5`, `4.000 AND 5.000`},

		// Boolean literals.
		{`true AND false`, `false`},
		{`true OR false`, `true`},
		{`true = false`, `false`},
		{`true != false`, `true`},
		{`true + false`, `true + false`},

		// Time literals.
		{`now() + 2h`, `"2000-01-01 02:00:00"`},
		{`now() / 2h`, `"2000-01-01 00:00:00" / 2h`},
		{`4µ + now()`, `"2000-01-01 00:00:00.000004"`},
		{`now() = now()`, `true`},
		{`now() != now()`, `false`},
		{`now() < now() + 1h`, `true`},
		{`now() <= now() + 1h`, `true`},
		{`now() >= now() - 1h`, `true`},
		{`now() > now() - 1h`, `true`},
		{`now() - (now() - 60s)`, `1m`},
		{`now() AND now()`, `"2000-01-01 00:00:00" AND "2000-01-01 00:00:00"`},

		// Duration literals.
		{`10m + 1h - 60s`, `69m`},
		{`(10m / 2) * 5`, `25m`},
		{`60s = 1m`, `true`},
		{`60s != 1m`, `false`},
		{`60s < 1h`, `true`},
		{`60s <= 1h`, `true`},
		{`60s > 12s`, `true`},
		{`60s >= 1m`, `true`},
		{`60s AND 1m`, `1m AND 1m`},
		{`60m / 0`, `0s`},
		{`60m + 50`, `1h + 50.000`},

		// String literals.
		{`"foo" + 'bar'`, `"foobar"`},
	} {
		// Fold expression.
		now := mustParseTime("2000-01-01T00:00:00Z")
		expr := influxql.Fold(MustParseExpr(tt.in), &now)

		// Compare with expected output.
		if out := expr.String(); tt.out != out {
			t.Errorf("%d. %s: unexpected expr:\n\nexp=%s\n\ngot=%s\n\n", i, tt.in, tt.out, out)
			continue
		}
	}
}

// Ensure an a "now()" call is not folded when now is not passed in.
func TestFold_WithoutNow(t *testing.T) {
	expr := influxql.Fold(MustParseExpr(`now()`), nil)
	if s := expr.String(); s != `now()` {
		t.Fatalf("unexpected expr: %s", s)
	}
}

// Ensure the time range of an expression can be extracted.
func TestTimeRange(t *testing.T) {
	for i, tt := range []struct {
		expr     string
		min, max string
	}{
		// LHS VarRef
		{expr: `time > "2000-01-01 00:00:00"`, min: `2000-01-01 00:00:00.000001`, max: `0001-01-01 00:00:00`},
		{expr: `time >= "2000-01-01 00:00:00"`, min: `2000-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
		{expr: `time < "2000-01-01 00:00:00"`, min: `0001-01-01 00:00:00`, max: `1999-12-31 23:59:59.999999`},
		{expr: `time <= "2000-01-01 00:00:00"`, min: `0001-01-01 00:00:00`, max: `2000-01-01 00:00:00`},

		// RHS VarRef
		{expr: `"2000-01-01 00:00:00" > time`, min: `0001-01-01 00:00:00`, max: `1999-12-31 23:59:59.999999`},
		{expr: `"2000-01-01 00:00:00" >= time`, min: `0001-01-01 00:00:00`, max: `2000-01-01 00:00:00`},
		{expr: `"2000-01-01 00:00:00" < time`, min: `2000-01-01 00:00:00.000001`, max: `0001-01-01 00:00:00`},
		{expr: `"2000-01-01 00:00:00" <= time`, min: `2000-01-01 00:00:00`, max: `0001-01-01 00:00:00`},

		// Equality
		{expr: `time = "2000-01-01 00:00:00"`, min: `2000-01-01 00:00:00`, max: `2000-01-01 00:00:00`},

		// Multiple time expressions.
		{expr: `time >= "2000-01-01 00:00:00" AND time < "2000-01-02 00:00:00"`, min: `2000-01-01 00:00:00`, max: `2000-01-01 23:59:59.999999`},

		// Non-comparative expressions.
		{expr: `time`, min: `0001-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
		{expr: `time + 2`, min: `0001-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
		{expr: `time - "2000-01-01 00:00:00"`, min: `0001-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
		{expr: `time AND "2000-01-01 00:00:00"`, min: `0001-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
	} {
		// Extract time range.
		expr := MustParseExpr(tt.expr)
		min, max := influxql.TimeRange(expr)

		// Compare with expected min/max.
		if min := min.Format(influxql.TimeFormat); tt.min != min {
			t.Errorf("%d. %s: unexpected min:\n\nexp=%s\n\ngot=%s\n\n", i, tt.expr, tt.min, min)
			continue
		}
		if max := max.Format(influxql.TimeFormat); tt.max != max {
			t.Errorf("%d. %s: unexpected max:\n\nexp=%s\n\ngot=%s\n\n", i, tt.expr, tt.max, max)
			continue
		}
	}
}
<commit_msg>Add time range crossover test.<commit_after>package influxql_test

import (
	"strings"
	"testing"

	"github.com/influxdb/influxdb/influxql"
)

// Ensure a value's data type can be retrieved.
func TestInspectDataType(t *testing.T) {
	for i, tt := range []struct {
		v   interface{}
		typ influxql.DataType
	}{
		{float64(100), influxql.Number},
	} {
		if typ := influxql.InspectDataType(tt.v); tt.typ != typ {
			t.Errorf("%d. %v (%s): unexpected type: %s", i, tt.v, tt.typ, typ)
			continue
		}
	}
}

// Ensure the SELECT statement can extract substatements.
func TestSelectStatement_Substatement(t *testing.T) {
	var tests = []struct {
		stmt string
		expr *influxql.VarRef
		sub  string
		err  string
	}{
		// 0. Single series
		{
			stmt: `SELECT value FROM myseries WHERE value > 1`,
			expr: &influxql.VarRef{Val: "value"},
			sub:  `SELECT value FROM myseries WHERE value > 1.000`,
		},

		// 1. Simple join
		{
			stmt: `SELECT sum(aa.value) + sum(bb.value) FROM aa JOIN bb`,
			expr: &influxql.VarRef{Val: "aa.value"},
			sub:  `SELECT aa.value FROM aa`,
		},

		// 2. Simple merge
		{
			stmt: `SELECT sum(aa.value) + sum(bb.value) FROM aa MERGE bb`,
			expr: &influxql.VarRef{Val: "bb.value"},
			sub:  `SELECT bb.value FROM bb`,
		},

		// 3. Join with condition
		{
			stmt: `SELECT sum(aa.value) + sum(bb.value) FROM aa JOIN bb WHERE aa.host = "servera" AND bb.host = "serverb"`,
			expr: &influxql.VarRef{Val: "bb.value"},
			sub:  `SELECT bb.value FROM bb WHERE bb.host = "serverb"`,
		},

		// 4. Join with complex condition
		{
			stmt: `SELECT sum(aa.value) + sum(bb.value) FROM aa JOIN bb WHERE aa.host = "servera" AND (bb.host = "serverb" OR bb.host = "serverc") AND 1 = 2`,
			expr: &influxql.VarRef{Val: "bb.value"},
			sub:  `SELECT bb.value FROM bb WHERE (bb.host = "serverb" OR bb.host = "serverc") AND 1.000 = 2.000`,
		},
	}

	for i, tt := range tests {
		// Parse statement.
		stmt, err := influxql.NewParser(strings.NewReader(tt.stmt)).ParseStatement()
		if err != nil {
			t.Fatalf("invalid statement: %q: %s", tt.stmt, err)
		}

		// Extract substatement.
		sub, err := stmt.(*influxql.SelectStatement).Substatement(tt.expr)
		if err != nil {
			t.Errorf("%d. %q: unexpected error: %s", i, tt.stmt, err)
			continue
		}
		if substr := sub.String(); tt.sub != substr {
			t.Errorf("%d. %q: unexpected substatement:\n\nexp=%s\n\ngot=%s\n\n", i, tt.stmt, tt.sub, substr)
			continue
		}
	}
}

// Ensure an expression can be folded.
func TestFold(t *testing.T) {
	for i, tt := range []struct {
		in  string
		out string
	}{
		// Number literals.
		{`1 + 2`, `3.000`},
		{`(foo*2) + (4/2) + (3 * 5) - 0.5`, `(foo * 2.000) + 16.500`},
		{`foo(bar(2 + 3), 4)`, `foo(bar(5.000), 4.000)`},
		{`4 / 0`, `0.000`},
		{`4 = 4`, `true`},
		{`4 != 4`, `false`},
		{`6 > 4`, `true`},
		{`4 >= 4`, `true`},
		{`4 < 6`, `true`},
		{`4 <= 4`, `true`},
		{`4 AND 5`, `4.000 AND 5.000`},

		// Boolean literals.
		{`true AND false`, `false`},
		{`true OR false`, `true`},
		{`true = false`, `false`},
		{`true != false`, `true`},
		{`true + false`, `true + false`},

		// Time literals.
		{`now() + 2h`, `"2000-01-01 02:00:00"`},
		{`now() / 2h`, `"2000-01-01 00:00:00" / 2h`},
		{`4µ + now()`, `"2000-01-01 00:00:00.000004"`},
		{`now() = now()`, `true`},
		{`now() != now()`, `false`},
		{`now() < now() + 1h`, `true`},
		{`now() <= now() + 1h`, `true`},
		{`now() >= now() - 1h`, `true`},
		{`now() > now() - 1h`, `true`},
		{`now() - (now() - 60s)`, `1m`},
		{`now() AND now()`, `"2000-01-01 00:00:00" AND "2000-01-01 00:00:00"`},

		// Duration literals.
		{`10m + 1h - 60s`, `69m`},
		{`(10m / 2) * 5`, `25m`},
		{`60s = 1m`, `true`},
		{`60s != 1m`, `false`},
		{`60s < 1h`, `true`},
		{`60s <= 1h`, `true`},
		{`60s > 12s`, `true`},
		{`60s >= 1m`, `true`},
		{`60s AND 1m`, `1m AND 1m`},
		{`60m / 0`, `0s`},
		{`60m + 50`, `1h + 50.000`},

		// String literals.
		{`"foo" + 'bar'`, `"foobar"`},
	} {
		// Fold expression.
		now := mustParseTime("2000-01-01T00:00:00Z")
		expr := influxql.Fold(MustParseExpr(tt.in), &now)

		// Compare with expected output.
		if out := expr.String(); tt.out != out {
			t.Errorf("%d. %s: unexpected expr:\n\nexp=%s\n\ngot=%s\n\n", i, tt.in, tt.out, out)
			continue
		}
	}
}

// Ensure an a "now()" call is not folded when now is not passed in.
func TestFold_WithoutNow(t *testing.T) {
	expr := influxql.Fold(MustParseExpr(`now()`), nil)
	if s := expr.String(); s != `now()` {
		t.Fatalf("unexpected expr: %s", s)
	}
}

// Ensure the time range of an expression can be extracted.
func TestTimeRange(t *testing.T) {
	for i, tt := range []struct {
		expr     string
		min, max string
	}{
		// LHS VarRef
		{expr: `time > "2000-01-01 00:00:00"`, min: `2000-01-01 00:00:00.000001`, max: `0001-01-01 00:00:00`},
		{expr: `time >= "2000-01-01 00:00:00"`, min: `2000-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
		{expr: `time < "2000-01-01 00:00:00"`, min: `0001-01-01 00:00:00`, max: `1999-12-31 23:59:59.999999`},
		{expr: `time <= "2000-01-01 00:00:00"`, min: `0001-01-01 00:00:00`, max: `2000-01-01 00:00:00`},

		// RHS VarRef
		{expr: `"2000-01-01 00:00:00" > time`, min: `0001-01-01 00:00:00`, max: `1999-12-31 23:59:59.999999`},
		{expr: `"2000-01-01 00:00:00" >= time`, min: `0001-01-01 00:00:00`, max: `2000-01-01 00:00:00`},
		{expr: `"2000-01-01 00:00:00" < time`, min: `2000-01-01 00:00:00.000001`, max: `0001-01-01 00:00:00`},
		{expr: `"2000-01-01 00:00:00" <= time`, min: `2000-01-01 00:00:00`, max: `0001-01-01 00:00:00`},

		// Equality
		{expr: `time = "2000-01-01 00:00:00"`, min: `2000-01-01 00:00:00`, max: `2000-01-01 00:00:00`},

		// Multiple time expressions.
		{expr: `time >= "2000-01-01 00:00:00" AND time < "2000-01-02 00:00:00"`, min: `2000-01-01 00:00:00`, max: `2000-01-01 23:59:59.999999`},

		// Min/max crossover
		{expr: `time >= "2000-01-01 00:00:00" AND time <= "1999-01-01 00:00:00"`, min: `2000-01-01 00:00:00`, max: `1999-01-01 00:00:00`},

		// Non-comparative expressions.
		{expr: `time`, min: `0001-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
		{expr: `time + 2`, min: `0001-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
		{expr: `time - "2000-01-01 00:00:00"`, min: `0001-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
		{expr: `time AND "2000-01-01 00:00:00"`, min: `0001-01-01 00:00:00`, max: `0001-01-01 00:00:00`},
	} {
		// Extract time range.
		expr := MustParseExpr(tt.expr)
		min, max := influxql.TimeRange(expr)

		// Compare with expected min/max.
		if min := min.Format(influxql.TimeFormat); tt.min != min {
			t.Errorf("%d. %s: unexpected min:\n\nexp=%s\n\ngot=%s\n\n", i, tt.expr, tt.min, min)
			continue
		}
		if max := max.Format(influxql.TimeFormat); tt.max != max {
			t.Errorf("%d. %s: unexpected max:\n\nexp=%s\n\ngot=%s\n\n", i, tt.expr, tt.max, max)
			continue
		}
	}
}
<|endoftext|>


<commit_before>package ingester

import (
	"fmt"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/prometheus/common/model"
	prom_chunk "github.com/prometheus/prometheus/storage/local/chunk"
)

const (
	discardReasonLabel = "reason"

	// Reasons to discard samples.
	outOfOrderTimestamp = "timestamp_out_of_order"
	duplicateSample     = "multiple_values_for_timestamp"
)

var (
	memorySeriesDesc = prometheus.NewDesc(
		"chronix_ingester_memory_series",
		"The current number of series in memory.",
		nil, nil,
	)

	// ErrOutOfOrderSample is returned if a sample has a timestamp before the latest
	// timestamp in the series it is appended to.
	ErrOutOfOrderSample = fmt.Errorf("sample timestamp out of order")
	// ErrDuplicateSampleForTimestamp is returned if a sample has the same
	// timestamp as the latest sample in the series it is appended to but a
	// different value. (Appending an identical sample is a no-op and does
	// not cause an error.)
	ErrDuplicateSampleForTimestamp = fmt.Errorf("sample with repeated timestamp but different value")
)

// A ChunkStore writes Prometheus chunks to a backing store.
type ChunkStore interface {
	Put(model.Metric, []*prom_chunk.Desc) error
}

// Config configures an Ingester.
type Config struct {
	FlushCheckPeriod     time.Duration
	MaxChunkAge          time.Duration
	MaxConcurrentFlushes int
}

// An Ingester batches up samples for multiple series and stores
// them as chunks in a ChunkStore.
type Ingester struct {
	// Configuration and lifecycle management.
	cfg        Config
	chunkStore ChunkStore
	stopLock   sync.RWMutex
	stopped    bool
	quit       chan struct{}
	done       chan struct{}

	// Sample ingestion state.
	fpLocker   *fingerprintLocker
	fpToSeries *seriesMap
	mapper     *fpMapper

	// Metrics about the Ingester itself.
	ingestedSamples    prometheus.Counter
	discardedSamples   *prometheus.CounterVec
	chunkUtilization   prometheus.Histogram
	chunkStoreFailures prometheus.Counter
	memoryChunks       prometheus.Gauge
}

// NewIngester constructs a new Ingester.
func NewIngester(cfg Config, chunkStore ChunkStore) *Ingester {
	if cfg.FlushCheckPeriod == 0 {
		cfg.FlushCheckPeriod = 1 * time.Minute
	}
	if cfg.MaxChunkAge == 0 {
		cfg.MaxChunkAge = 30 * time.Minute
	}
	if cfg.MaxConcurrentFlushes == 0 {
		cfg.MaxConcurrentFlushes = 100
	}

	sm := newSeriesMap()
	i := &Ingester{
		cfg:        cfg,
		chunkStore: chunkStore,
		quit:       make(chan struct{}),
		done:       make(chan struct{}),

		fpToSeries: sm,
		fpLocker:   newFingerprintLocker(16),
		mapper:     newFPMapper(sm),

		ingestedSamples: prometheus.NewCounter(prometheus.CounterOpts{
			Name: "chronix_ingester_ingested_samples_total",
			Help: "The total number of samples ingested.",
		}),
		discardedSamples: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "chronix_ingester_out_of_order_samples_total",
				Help: "The total number of samples that were discarded because their timestamps were at or before the last received sample for a series.",
			},
			[]string{discardReasonLabel},
		),
		chunkUtilization: prometheus.NewHistogram(prometheus.HistogramOpts{
			Name:    "chronix_ingester_chunk_utilization",
			Help:    "Distribution of stored chunk utilization.",
			Buckets: []float64{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9},
		}),
		memoryChunks: prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "chronix_ingester_memory_chunks",
			Help: "The total number of samples returned from queries.",
		}),
		chunkStoreFailures: prometheus.NewCounter(prometheus.CounterOpts{
			Name: "chronix_ingester_chunk_store_failures_total",
			Help: "The total number of errors while storing chunks to the chunk store.",
		}),
	}

	go i.loop()
	return i
}

// NeedsThrottling implements storage.SampleAppender.
func (*Ingester) NeedsThrottling() bool {
	// Always return false for now - this method is only there to implement the interface.
	return false
}

// Append implements storage.SampleAppender.
func (i *Ingester) Append(sample *model.Sample) error {
	for ln, lv := range sample.Metric {
		if len(lv) == 0 {
			delete(sample.Metric, ln)
		}
	}

	i.stopLock.RLock()
	defer i.stopLock.RUnlock()
	if i.stopped {
		return fmt.Errorf("ingester stopping")
	}

	fp, series := i.getOrCreateSeries(sample.Metric)
	defer func() {
		i.fpLocker.Unlock(fp)
	}()

	if sample.Timestamp == series.lastTime {
		// Don't report "no-op appends", i.e. where timestamp and sample
		// value are the same as for the last append, as they are a
		// common occurrence when using client-side timestamps
		// (e.g. Pushgateway or federation).
		if sample.Timestamp == series.lastTime &&
			series.lastSampleValueSet &&
			sample.Value.Equal(series.lastSampleValue) {
			return nil
		}
		i.discardedSamples.WithLabelValues(duplicateSample).Inc()
		return ErrDuplicateSampleForTimestamp // Caused by the caller.
	}
	if sample.Timestamp < series.lastTime {
		i.discardedSamples.WithLabelValues(outOfOrderTimestamp).Inc()
		return ErrOutOfOrderSample // Caused by the caller.
	}
	prevNumChunks := len(series.chunkDescs)
	_, err := series.add(model.SamplePair{
		Value:     sample.Value,
		Timestamp: sample.Timestamp,
	})
	i.memoryChunks.Add(float64(len(series.chunkDescs) - prevNumChunks))

	if err == nil {
		// TODO: Track append failures too (unlikely to happen).
		i.ingestedSamples.Inc()
	}
	return err
}

func (i *Ingester) getOrCreateSeries(metric model.Metric) (model.Fingerprint, *memorySeries) {
	rawFP := metric.FastFingerprint()
	i.fpLocker.Lock(rawFP)
	fp := i.mapper.mapFP(rawFP, metric)
	if fp != rawFP {
		i.fpLocker.Unlock(rawFP)
		i.fpLocker.Lock(fp)
	}

	series, ok := i.fpToSeries.get(fp)
	if ok {
		return fp, series
	}

	series = newMemorySeries(metric)
	i.fpToSeries.put(fp, series)
	return fp, series
}

// Stop stops the Ingester.
func (i *Ingester) Stop() {
	i.stopLock.Lock()
	i.stopped = true
	i.stopLock.Unlock()

	close(i.quit)
	<-i.done
}

func (i *Ingester) loop() {
	defer func() {
		i.flushAllSeries(true)
		close(i.done)
		log.Infof("Ingester exited gracefully")
	}()

	tick := time.Tick(i.cfg.FlushCheckPeriod)
	for {
		select {
		case <-tick:
			i.flushAllSeries(false)
		case <-i.quit:
			return
		}
	}
}

func (i *Ingester) flushAllSeries(immediate bool) {
	var wg sync.WaitGroup
	semaphore := make(chan struct{}, i.cfg.MaxConcurrentFlushes)
	for pair := range i.fpToSeries.iter() {
		wg.Add(1)
		semaphore <- struct{}{}
		go func(pair fingerprintSeriesPair) {
			if err := i.flushSeries(pair.fp, pair.series, immediate); err != nil {
				log.Errorf("Failed to flush chunks for series: %v", err)
			}
			<-semaphore
			wg.Done()
		}(pair)
	}
	wg.Wait()
}

func (i *Ingester) flushSeries(fp model.Fingerprint, series *memorySeries, immediate bool) error {
	i.fpLocker.Lock(fp)

	// Decide what chunks to flush.
	if immediate || time.Now().Sub(series.head().FirstTime().Time()) > i.cfg.MaxChunkAge {
		series.headChunkClosed = true
	}
	chunks := series.chunkDescs
	if !series.headChunkClosed {
		chunks = chunks[:len(chunks)-1]
	}
	i.fpLocker.Unlock(fp)
	if len(chunks) == 0 {
		return nil
	}

	// Flush the chunks without locking the series.
	if err := i.chunkStore.Put(series.metric, chunks); err != nil {
		i.chunkStoreFailures.Add(float64(len(chunks)))
		return err
	}
	for _, c := range chunks {
		i.chunkUtilization.Observe(c.C.Utilization())
	}

	// Now remove the chunks.
	i.fpLocker.Lock(fp)
	series.chunkDescs = series.chunkDescs[len(chunks):]
	i.memoryChunks.Sub(float64(len(chunks)))
	if len(series.chunkDescs) == 0 {
		i.fpToSeries.del(fp)
	}
	i.fpLocker.Unlock(fp)
	return nil
}

// Describe implements prometheus.Collector.
func (i *Ingester) Describe(ch chan<- *prometheus.Desc) {
	ch <- memorySeriesDesc
	ch <- i.ingestedSamples.Desc()
	i.discardedSamples.Describe(ch)
	ch <- i.chunkUtilization.Desc()
	ch <- i.chunkStoreFailures.Desc()
}

// Collect implements prometheus.Collector.
func (i *Ingester) Collect(ch chan<- prometheus.Metric) {
	ch <- prometheus.MustNewConstMetric(
		memorySeriesDesc,
		prometheus.GaugeValue,
		float64(i.fpToSeries.length()),
	)
	ch <- i.ingestedSamples
	i.discardedSamples.Collect(ch)
	ch <- i.chunkUtilization
	ch <- i.chunkStoreFailures
}
<commit_msg>Fix exposure and help string of chunks in memory metric<commit_after>package ingester

import (
	"fmt"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/prometheus/common/model"
	prom_chunk "github.com/prometheus/prometheus/storage/local/chunk"
)

const (
	discardReasonLabel = "reason"

	// Reasons to discard samples.
	outOfOrderTimestamp = "timestamp_out_of_order"
	duplicateSample     = "multiple_values_for_timestamp"
)

var (
	memorySeriesDesc = prometheus.NewDesc(
		"chronix_ingester_memory_series",
		"The current number of series in memory.",
		nil, nil,
	)

	// ErrOutOfOrderSample is returned if a sample has a timestamp before the latest
	// timestamp in the series it is appended to.
	ErrOutOfOrderSample = fmt.Errorf("sample timestamp out of order")
	// ErrDuplicateSampleForTimestamp is returned if a sample has the same
	// timestamp as the latest sample in the series it is appended to but a
	// different value. (Appending an identical sample is a no-op and does
	// not cause an error.)
	ErrDuplicateSampleForTimestamp = fmt.Errorf("sample with repeated timestamp but different value")
)

// A ChunkStore writes Prometheus chunks to a backing store.
type ChunkStore interface {
	Put(model.Metric, []*prom_chunk.Desc) error
}

// Config configures an Ingester.
type Config struct {
	FlushCheckPeriod     time.Duration
	MaxChunkAge          time.Duration
	MaxConcurrentFlushes int
}

// An Ingester batches up samples for multiple series and stores
// them as chunks in a ChunkStore.
type Ingester struct {
	// Configuration and lifecycle management.
	cfg        Config
	chunkStore ChunkStore
	stopLock   sync.RWMutex
	stopped    bool
	quit       chan struct{}
	done       chan struct{}

	// Sample ingestion state.
	fpLocker   *fingerprintLocker
	fpToSeries *seriesMap
	mapper     *fpMapper

	// Metrics about the Ingester itself.
	ingestedSamples    prometheus.Counter
	discardedSamples   *prometheus.CounterVec
	chunkUtilization   prometheus.Histogram
	chunkStoreFailures prometheus.Counter
	memoryChunks       prometheus.Gauge
}

// NewIngester constructs a new Ingester.
func NewIngester(cfg Config, chunkStore ChunkStore) *Ingester {
	if cfg.FlushCheckPeriod == 0 {
		cfg.FlushCheckPeriod = 1 * time.Minute
	}
	if cfg.MaxChunkAge == 0 {
		cfg.MaxChunkAge = 30 * time.Minute
	}
	if cfg.MaxConcurrentFlushes == 0 {
		cfg.MaxConcurrentFlushes = 100
	}

	sm := newSeriesMap()
	i := &Ingester{
		cfg:        cfg,
		chunkStore: chunkStore,
		quit:       make(chan struct{}),
		done:       make(chan struct{}),

		fpToSeries: sm,
		fpLocker:   newFingerprintLocker(16),
		mapper:     newFPMapper(sm),

		ingestedSamples: prometheus.NewCounter(prometheus.CounterOpts{
			Name: "chronix_ingester_ingested_samples_total",
			Help: "The total number of samples ingested.",
		}),
		discardedSamples: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "chronix_ingester_out_of_order_samples_total",
				Help: "The total number of samples that were discarded because their timestamps were at or before the last received sample for a series.",
			},
			[]string{discardReasonLabel},
		),
		chunkUtilization: prometheus.NewHistogram(prometheus.HistogramOpts{
			Name:    "chronix_ingester_chunk_utilization",
			Help:    "Distribution of stored chunk utilization.",
			Buckets: []float64{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9},
		}),
		memoryChunks: prometheus.NewGauge(prometheus.GaugeOpts{
			Name: "chronix_ingester_memory_chunks",
			Help: "The total number of chunks in memory.",
		}),
		chunkStoreFailures: prometheus.NewCounter(prometheus.CounterOpts{
			Name: "chronix_ingester_chunk_store_failures_total",
			Help: "The total number of errors while storing chunks to the chunk store.",
		}),
	}

	go i.loop()
	return i
}

// NeedsThrottling implements storage.SampleAppender.
func (*Ingester) NeedsThrottling() bool {
	// Always return false for now - this method is only there to implement the interface.
	return false
}

// Append implements storage.SampleAppender.
func (i *Ingester) Append(sample *model.Sample) error {
	for ln, lv := range sample.Metric {
		if len(lv) == 0 {
			delete(sample.Metric, ln)
		}
	}

	i.stopLock.RLock()
	defer i.stopLock.RUnlock()
	if i.stopped {
		return fmt.Errorf("ingester stopping")
	}

	fp, series := i.getOrCreateSeries(sample.Metric)
	defer func() {
		i.fpLocker.Unlock(fp)
	}()

	if sample.Timestamp == series.lastTime {
		// Don't report "no-op appends", i.e. where timestamp and sample
		// value are the same as for the last append, as they are a
		// common occurrence when using client-side timestamps
		// (e.g. Pushgateway or federation).
		if sample.Timestamp == series.lastTime &&
			series.lastSampleValueSet &&
			sample.Value.Equal(series.lastSampleValue) {
			return nil
		}
		i.discardedSamples.WithLabelValues(duplicateSample).Inc()
		return ErrDuplicateSampleForTimestamp // Caused by the caller.
	}
	if sample.Timestamp < series.lastTime {
		i.discardedSamples.WithLabelValues(outOfOrderTimestamp).Inc()
		return ErrOutOfOrderSample // Caused by the caller.
	}
	prevNumChunks := len(series.chunkDescs)
	_, err := series.add(model.SamplePair{
		Value:     sample.Value,
		Timestamp: sample.Timestamp,
	})
	i.memoryChunks.Add(float64(len(series.chunkDescs) - prevNumChunks))

	if err == nil {
		// TODO: Track append failures too (unlikely to happen).
		i.ingestedSamples.Inc()
	}
	return err
}

func (i *Ingester) getOrCreateSeries(metric model.Metric) (model.Fingerprint, *memorySeries) {
	rawFP := metric.FastFingerprint()
	i.fpLocker.Lock(rawFP)
	fp := i.mapper.mapFP(rawFP, metric)
	if fp != rawFP {
		i.fpLocker.Unlock(rawFP)
		i.fpLocker.Lock(fp)
	}

	series, ok := i.fpToSeries.get(fp)
	if ok {
		return fp, series
	}

	series = newMemorySeries(metric)
	i.fpToSeries.put(fp, series)
	return fp, series
}

// Stop stops the Ingester.
func (i *Ingester) Stop() {
	i.stopLock.Lock()
	i.stopped = true
	i.stopLock.Unlock()

	close(i.quit)
	<-i.done
}

func (i *Ingester) loop() {
	defer func() {
		i.flushAllSeries(true)
		close(i.done)
		log.Infof("Ingester exited gracefully")
	}()

	tick := time.Tick(i.cfg.FlushCheckPeriod)
	for {
		select {
		case <-tick:
			i.flushAllSeries(false)
		case <-i.quit:
			return
		}
	}
}

func (i *Ingester) flushAllSeries(immediate bool) {
	var wg sync.WaitGroup
	semaphore := make(chan struct{}, i.cfg.MaxConcurrentFlushes)
	for pair := range i.fpToSeries.iter() {
		wg.Add(1)
		semaphore <- struct{}{}
		go func(pair fingerprintSeriesPair) {
			if err := i.flushSeries(pair.fp, pair.series, immediate); err != nil {
				log.Errorf("Failed to flush chunks for series: %v", err)
			}
			<-semaphore
			wg.Done()
		}(pair)
	}
	wg.Wait()
}

func (i *Ingester) flushSeries(fp model.Fingerprint, series *memorySeries, immediate bool) error {
	i.fpLocker.Lock(fp)

	// Decide what chunks to flush.
	if immediate || time.Now().Sub(series.head().FirstTime().Time()) > i.cfg.MaxChunkAge {
		series.headChunkClosed = true
	}
	chunks := series.chunkDescs
	if !series.headChunkClosed {
		chunks = chunks[:len(chunks)-1]
	}
	i.fpLocker.Unlock(fp)
	if len(chunks) == 0 {
		return nil
	}

	// Flush the chunks without locking the series.
	if err := i.chunkStore.Put(series.metric, chunks); err != nil {
		i.chunkStoreFailures.Add(float64(len(chunks)))
		return err
	}
	for _, c := range chunks {
		i.chunkUtilization.Observe(c.C.Utilization())
	}

	// Now remove the chunks.
	i.fpLocker.Lock(fp)
	series.chunkDescs = series.chunkDescs[len(chunks):]
	i.memoryChunks.Sub(float64(len(chunks)))
	if len(series.chunkDescs) == 0 {
		i.fpToSeries.del(fp)
	}
	i.fpLocker.Unlock(fp)
	return nil
}

// Describe implements prometheus.Collector.
func (i *Ingester) Describe(ch chan<- *prometheus.Desc) {
	ch <- memorySeriesDesc
	ch <- i.ingestedSamples.Desc()
	i.discardedSamples.Describe(ch)
	ch <- i.chunkUtilization.Desc()
	ch <- i.chunkStoreFailures.Desc()
	ch <- i.memoryChunks.Desc()
}

// Collect implements prometheus.Collector.
func (i *Ingester) Collect(ch chan<- prometheus.Metric) {
	ch <- prometheus.MustNewConstMetric(
		memorySeriesDesc,
		prometheus.GaugeValue,
		float64(i.fpToSeries.length()),
	)
	ch <- i.ingestedSamples
	i.discardedSamples.Collect(ch)
	ch <- i.chunkUtilization
	ch <- i.chunkStoreFailures
	ch <- i.memoryChunks
}
<|endoftext|>