<commit_before>package bridge

import (
	"errors"
	"fmt"
	"net"

	"github.com/docker/libnetwork/iptables"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
)

// DockerChain: DOCKER iptable chain name
const (
	DockerChain = "DOCKER"
	// Isolation between bridge networks is achieved in two stages by means
	// of the following two chains in the filter table. The first chain matches
	// on the source interface being a bridge network's bridge and the
	// destination being a different interface. A positive match leads to the
	// second isolation chain. No match returns to the parent chain. The second
	// isolation chain matches on destination interface being a bridge network's
	// bridge. A positive match identifies a packet originated from one bridge
	// network's bridge destined to another bridge network's bridge and will
	// result in the packet being dropped. No match returns to the parent chain.
	IsolationChain1 = "DOCKER-ISOLATION-STAGE-1"
	IsolationChain2 = "DOCKER-ISOLATION-STAGE-2"
)

func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
	// Sanity check.
	if config.EnableIPTables == false {
		return nil, nil, nil, nil, errors.New("cannot create new chains, EnableIPTable is disabled")
	}

	hairpinMode := !config.EnableUserlandProxy

	natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to create NAT chain: %v", err)
	}
	defer func() {
		if err != nil {
			if err := iptables.RemoveExistingChain(DockerChain, iptables.Nat); err != nil {
				logrus.Warnf("failed on removing iptables NAT chain on cleanup: %v", err)
			}
		}
	}()

	filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, false)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER chain: %v", err)
	}
	defer func() {
		if err != nil {
			if err := iptables.RemoveExistingChain(DockerChain, iptables.Filter); err != nil {
				logrus.Warnf("failed on removing iptables FILTER chain on cleanup: %v", err)
			}
		}
	}()

	isolationChain1, err := iptables.NewChain(IsolationChain1, iptables.Filter, false)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
	}

	isolationChain2, err := iptables.NewChain(IsolationChain2, iptables.Filter, false)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
	}

	if err := iptables.AddReturnRule(IsolationChain1); err != nil {
		return nil, nil, nil, nil, err
	}

	if err := iptables.AddReturnRule(IsolationChain2); err != nil {
		return nil, nil, nil, nil, err
	}

	return natChain, filterChain, isolationChain1, isolationChain2, nil
}

func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error {
	var err error

	d := n.driver
	d.Lock()
	driverConfig := d.config
	d.Unlock()

	// Sanity check.
	if driverConfig.EnableIPTables == false {
		return errors.New("Cannot program chains, EnableIPTable is disabled")
	}

	// Pickup this configuration option from driver
	hairpinMode := !driverConfig.EnableUserlandProxy

	maskedAddrv4 := &net.IPNet{
		IP:   i.bridgeIPv4.IP.Mask(i.bridgeIPv4.Mask),
		Mask: i.bridgeIPv4.Mask,
	}
	if config.Internal {
		if err = setupInternalNetworkRules(config.BridgeName, maskedAddrv4, config.EnableICC, true); err != nil {
			return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
		}
		n.registerIptCleanFunc(func() error {
			return setupInternalNetworkRules(config.BridgeName, maskedAddrv4, config.EnableICC, false)
		})
	} else {
		if err = setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil {
			return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
		}
		n.registerIptCleanFunc(func() error {
			return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
		})
		natChain, filterChain, _, _, err := n.getDriverChains()
		if err != nil {
			return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error())
		}

		err = iptables.ProgramChain(natChain, config.BridgeName, hairpinMode, true)
		if err != nil {
			return fmt.Errorf("Failed to program NAT chain: %s", err.Error())
		}

		err = iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, true)
		if err != nil {
			return fmt.Errorf("Failed to program FILTER chain: %s", err.Error())
		}

		n.registerIptCleanFunc(func() error {
			return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false)
		})

		n.portMapper.SetIptablesChain(natChain, n.getNetworkBridgeName())
	}

	d.Lock()
	err = iptables.EnsureJumpRule("FORWARD", IsolationChain1)
	d.Unlock()
	if err != nil {
		return err
	}

	return nil
}

type iptRule struct {
	table   iptables.Table
	chain   string
	preArgs []string
	args    []string
}

func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairpin, enable bool) error {

	var (
		address   = addr.String()
		natRule   = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"}}
		hpNatRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}}
		skipDNAT  = iptRule{table: iptables.Nat, chain: DockerChain, preArgs: []string{"-t", "nat"}, args: []string{"-i", bridgeIface, "-j", "RETURN"}}
		outRule   = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}}
	)

	// Set NAT.
	if ipmasq {
		if err := programChainRule(natRule, "NAT", enable); err != nil {
			return err
		}
	}

	if ipmasq && !hairpin {
		if err := programChainRule(skipDNAT, "SKIP DNAT", enable); err != nil {
			return err
		}
	}

	// In hairpin mode, masquerade traffic from localhost
	if hairpin {
		if err := programChainRule(hpNatRule, "MASQ LOCAL HOST", enable); err != nil {
			return err
		}
	}

	// Set Inter Container Communication.
	if err := setIcc(bridgeIface, icc, enable); err != nil {
		return err
	}

	// Set Accept on all non-intercontainer outgoing packets.
	return programChainRule(outRule, "ACCEPT NON_ICC OUTGOING", enable)
}

func programChainRule(rule iptRule, ruleDescr string, insert bool) error {
	var (
		prefix    []string
		operation string
		condition bool
		doesExist = iptables.Exists(rule.table, rule.chain, rule.args...)
	)

	if insert {
		condition = !doesExist
		prefix = []string{"-I", rule.chain}
		operation = "enable"
	} else {
		condition = doesExist
		prefix = []string{"-D", rule.chain}
		operation = "disable"
	}
	if rule.preArgs != nil {
		prefix = append(rule.preArgs, prefix...)
	}

	if condition {
		if err := iptables.RawCombinedOutput(append(prefix, rule.args...)...); err != nil {
			return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
		}
	}

	return nil
}

func setIcc(bridgeIface string, iccEnable, insert bool) error {
	var (
		table      = iptables.Filter
		chain      = "FORWARD"
		args       = []string{"-i", bridgeIface, "-o", bridgeIface, "-j"}
		acceptArgs = append(args, "ACCEPT")
		dropArgs   = append(args, "DROP")
	)

	if insert {
		if !iccEnable {
			iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)

			if !iptables.Exists(table, chain, dropArgs...) {
				if err := iptables.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil {
					return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
				}
			}
		} else {
			iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)

			if !iptables.Exists(table, chain, acceptArgs...) {
				if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
					return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
				}
			}
		}
	} else {
		// Remove any ICC rule.
		if !iccEnable {
			if iptables.Exists(table, chain, dropArgs...) {
				iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
			}
		} else {
			if iptables.Exists(table, chain, acceptArgs...) {
				iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
			}
		}
	}

	return nil
}

// Control Inter Network Communication. Install[Remove] only if it is [not] present.
func setINC(iface string, enable bool) error {
	var (
		action    = iptables.Insert
		actionMsg = "add"
		chains    = []string{IsolationChain1, IsolationChain2}
		rules     = [][]string{
			{"-i", iface, "!", "-o", iface, "-j", IsolationChain2},
			{"-o", iface, "-j", "DROP"},
		}
	)

	if !enable {
		action = iptables.Delete
		actionMsg = "remove"
	}

	for i, chain := range chains {
		if err := iptables.ProgramRule(iptables.Filter, chain, action, rules[i]); err != nil {
			msg := fmt.Sprintf("unable to %s inter-network communication rule: %v", actionMsg, err)
			if enable {
				if i == 1 {
					// Rollback the rule installed on first chain
					if err2 := iptables.ProgramRule(iptables.Filter, chains[0], iptables.Delete, rules[0]); err2 != nil {
						logrus.Warn("Failed to rollback iptables rule after failure (%v): %v", err, err2)
					}
				}
				return fmt.Errorf(msg)
			}
			logrus.Warn(msg)
		}
	}

	return nil
}

// Obsolete chain from previous docker versions
const oldIsolationChain = "DOCKER-ISOLATION"

func removeIPChains() {
	// Remove obsolete rules from default chains
	iptables.ProgramRule(iptables.Filter, "FORWARD", iptables.Delete, []string{"-j", oldIsolationChain})

	// Remove chains
	for _, chainInfo := range []iptables.ChainInfo{
		{Name: DockerChain, Table: iptables.Nat},
		{Name: DockerChain, Table: iptables.Filter},
		{Name: IsolationChain1, Table: iptables.Filter},
		{Name: IsolationChain2, Table: iptables.Filter},
		{Name: oldIsolationChain, Table: iptables.Filter},
	} {
		if err := chainInfo.Remove(); err != nil {
			logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err)
		}
	}
}

func setupInternalNetworkRules(bridgeIface string, addr net.Addr, icc, insert bool) error {
	var (
		inDropRule  = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}}
		outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}}
	)
	if err := programChainRule(inDropRule, "DROP INCOMING", insert); err != nil {
		return err
	}
	if err := programChainRule(outDropRule, "DROP OUTGOING", insert); err != nil {
		return err
	}
	// Set Inter Container Communication.
	return setIcc(bridgeIface, icc, insert)
}

func clearEndpointConnections(nlh *netlink.Handle, ep *bridgeEndpoint) {
	var ipv4List []net.IP
	var ipv6List []net.IP
	if ep.addr != nil {
		ipv4List = append(ipv4List, ep.addr.IP)
	}
	if ep.addrv6 != nil {
		ipv6List = append(ipv6List, ep.addrv6.IP)
	}
	iptables.DeleteConntrackEntries(nlh, ipv4List, ipv6List)
}
<commit_msg>bridge: fix handling errors during setupIPChains()<commit_after>package bridge

import (
	"errors"
	"fmt"
	"net"

	"github.com/docker/libnetwork/iptables"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
)

// DockerChain: DOCKER iptable chain name
const (
	DockerChain = "DOCKER"
	// Isolation between bridge networks is achieved in two stages by means
	// of the following two chains in the filter table. The first chain matches
	// on the source interface being a bridge network's bridge and the
	// destination being a different interface. A positive match leads to the
	// second isolation chain. No match returns to the parent chain. The second
	// isolation chain matches on destination interface being a bridge network's
	// bridge. A positive match identifies a packet originated from one bridge
	// network's bridge destined to another bridge network's bridge and will
	// result in the packet being dropped. No match returns to the parent chain.
	IsolationChain1 = "DOCKER-ISOLATION-STAGE-1"
	IsolationChain2 = "DOCKER-ISOLATION-STAGE-2"
)

func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
	// Sanity check.
	if config.EnableIPTables == false {
		return nil, nil, nil, nil, errors.New("cannot create new chains, EnableIPTable is disabled")
	}

	hairpinMode := !config.EnableUserlandProxy

	natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to create NAT chain %s: %v", DockerChain, err)
	}
	defer func() {
		if err != nil {
			if err := iptables.RemoveExistingChain(DockerChain, iptables.Nat); err != nil {
				logrus.Warnf("failed on removing iptables NAT chain %s on cleanup: %v", DockerChain, err)
			}
		}
	}()

	filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, false)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER chain %s: %v", DockerChain, err)
	}
	defer func() {
		if err != nil {
			if err := iptables.RemoveExistingChain(DockerChain, iptables.Filter); err != nil {
				logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", DockerChain, err)
			}
		}
	}()

	isolationChain1, err := iptables.NewChain(IsolationChain1, iptables.Filter, false)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
	}
	defer func() {
		if err != nil {
			if err := iptables.RemoveExistingChain(IsolationChain1, iptables.Filter); err != nil {
				logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain1, err)
			}
		}
	}()

	isolationChain2, err := iptables.NewChain(IsolationChain2, iptables.Filter, false)
	if err != nil {
		return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
	}
	defer func() {
		if err != nil {
			if err := iptables.RemoveExistingChain(IsolationChain2, iptables.Filter); err != nil {
				logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain2, err)
			}
		}
	}()

	if err := iptables.AddReturnRule(IsolationChain1); err != nil {
		return nil, nil, nil, nil, err
	}

	if err := iptables.AddReturnRule(IsolationChain2); err != nil {
		return nil, nil, nil, nil, err
	}

	return natChain, filterChain, isolationChain1, isolationChain2, nil
}

func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error {
	var err error

	d := n.driver
	d.Lock()
	driverConfig := d.config
	d.Unlock()

	// Sanity check.
	if driverConfig.EnableIPTables == false {
		return errors.New("Cannot program chains, EnableIPTable is disabled")
	}

	// Pickup this configuration option from driver
	hairpinMode := !driverConfig.EnableUserlandProxy

	maskedAddrv4 := &net.IPNet{
		IP:   i.bridgeIPv4.IP.Mask(i.bridgeIPv4.Mask),
		Mask: i.bridgeIPv4.Mask,
	}
	if config.Internal {
		if err = setupInternalNetworkRules(config.BridgeName, maskedAddrv4, config.EnableICC, true); err != nil {
			return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
		}
		n.registerIptCleanFunc(func() error {
			return setupInternalNetworkRules(config.BridgeName, maskedAddrv4, config.EnableICC, false)
		})
	} else {
		if err = setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil {
			return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
		}
		n.registerIptCleanFunc(func() error {
			return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
		})
		natChain, filterChain, _, _, err := n.getDriverChains()
		if err != nil {
			return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error())
		}

		err = iptables.ProgramChain(natChain, config.BridgeName, hairpinMode, true)
		if err != nil {
			return fmt.Errorf("Failed to program NAT chain: %s", err.Error())
		}

		err = iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, true)
		if err != nil {
			return fmt.Errorf("Failed to program FILTER chain: %s", err.Error())
		}

		n.registerIptCleanFunc(func() error {
			return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false)
		})

		n.portMapper.SetIptablesChain(natChain, n.getNetworkBridgeName())
	}

	d.Lock()
	err = iptables.EnsureJumpRule("FORWARD", IsolationChain1)
	d.Unlock()
	if err != nil {
		return err
	}

	return nil
}

type iptRule struct {
	table   iptables.Table
	chain   string
	preArgs []string
	args    []string
}

func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairpin, enable bool) error {

	var (
		address   = addr.String()
		natRule   = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"}}
		hpNatRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}}
		skipDNAT  = iptRule{table: iptables.Nat, chain: DockerChain, preArgs: []string{"-t", "nat"}, args: []string{"-i", bridgeIface, "-j", "RETURN"}}
		outRule   = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}}
	)

	// Set NAT.
	if ipmasq {
		if err := programChainRule(natRule, "NAT", enable); err != nil {
			return err
		}
	}

	if ipmasq && !hairpin {
		if err := programChainRule(skipDNAT, "SKIP DNAT", enable); err != nil {
			return err
		}
	}

	// In hairpin mode, masquerade traffic from localhost
	if hairpin {
		if err := programChainRule(hpNatRule, "MASQ LOCAL HOST", enable); err != nil {
			return err
		}
	}

	// Set Inter Container Communication.
	if err := setIcc(bridgeIface, icc, enable); err != nil {
		return err
	}

	// Set Accept on all non-intercontainer outgoing packets.
	return programChainRule(outRule, "ACCEPT NON_ICC OUTGOING", enable)
}

func programChainRule(rule iptRule, ruleDescr string, insert bool) error {
	var (
		prefix    []string
		operation string
		condition bool
		doesExist = iptables.Exists(rule.table, rule.chain, rule.args...)
	)

	if insert {
		condition = !doesExist
		prefix = []string{"-I", rule.chain}
		operation = "enable"
	} else {
		condition = doesExist
		prefix = []string{"-D", rule.chain}
		operation = "disable"
	}
	if rule.preArgs != nil {
		prefix = append(rule.preArgs, prefix...)
	}

	if condition {
		if err := iptables.RawCombinedOutput(append(prefix, rule.args...)...); err != nil {
			return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
		}
	}

	return nil
}

func setIcc(bridgeIface string, iccEnable, insert bool) error {
	var (
		table      = iptables.Filter
		chain      = "FORWARD"
		args       = []string{"-i", bridgeIface, "-o", bridgeIface, "-j"}
		acceptArgs = append(args, "ACCEPT")
		dropArgs   = append(args, "DROP")
	)

	if insert {
		if !iccEnable {
			iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)

			if !iptables.Exists(table, chain, dropArgs...) {
				if err := iptables.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil {
					return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
				}
			}
		} else {
			iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)

			if !iptables.Exists(table, chain, acceptArgs...) {
				if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
					return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
				}
			}
		}
	} else {
		// Remove any ICC rule.
		if !iccEnable {
			if iptables.Exists(table, chain, dropArgs...) {
				iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
			}
		} else {
			if iptables.Exists(table, chain, acceptArgs...) {
				iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
			}
		}
	}

	return nil
}

// Control Inter Network Communication. Install[Remove] only if it is [not] present.
func setINC(iface string, enable bool) error {
	var (
		action    = iptables.Insert
		actionMsg = "add"
		chains    = []string{IsolationChain1, IsolationChain2}
		rules     = [][]string{
			{"-i", iface, "!", "-o", iface, "-j", IsolationChain2},
			{"-o", iface, "-j", "DROP"},
		}
	)

	if !enable {
		action = iptables.Delete
		actionMsg = "remove"
	}

	for i, chain := range chains {
		if err := iptables.ProgramRule(iptables.Filter, chain, action, rules[i]); err != nil {
			msg := fmt.Sprintf("unable to %s inter-network communication rule: %v", actionMsg, err)
			if enable {
				if i == 1 {
					// Rollback the rule installed on first chain
					if err2 := iptables.ProgramRule(iptables.Filter, chains[0], iptables.Delete, rules[0]); err2 != nil {
						logrus.Warn("Failed to rollback iptables rule after failure (%v): %v", err, err2)
					}
				}
				return fmt.Errorf(msg)
			}
			logrus.Warn(msg)
		}
	}

	return nil
}

// Obsolete chain from previous docker versions
const oldIsolationChain = "DOCKER-ISOLATION"

func removeIPChains() {
	// Remove obsolete rules from default chains
	iptables.ProgramRule(iptables.Filter, "FORWARD", iptables.Delete, []string{"-j", oldIsolationChain})

	// Remove chains
	for _, chainInfo := range []iptables.ChainInfo{
		{Name: DockerChain, Table: iptables.Nat},
		{Name: DockerChain, Table: iptables.Filter},
		{Name: IsolationChain1, Table: iptables.Filter},
		{Name: IsolationChain2, Table: iptables.Filter},
		{Name: oldIsolationChain, Table: iptables.Filter},
	} {
		if err := chainInfo.Remove(); err != nil {
			logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err)
		}
	}
}

func setupInternalNetworkRules(bridgeIface string, addr net.Addr, icc, insert bool) error {
	var (
		inDropRule  = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}}
		outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}}
	)
	if err := programChainRule(inDropRule, "DROP INCOMING", insert); err != nil {
		return err
	}
	if err := programChainRule(outDropRule, "DROP OUTGOING", insert); err != nil {
		return err
	}
	// Set Inter Container Communication.
	return setIcc(bridgeIface, icc, insert)
}

func clearEndpointConnections(nlh *netlink.Handle, ep *bridgeEndpoint) {
	var ipv4List []net.IP
	var ipv6List []net.IP
	if ep.addr != nil {
		ipv4List = append(ipv4List, ep.addr.IP)
	}
	if ep.addrv6 != nil {
		ipv6List = append(ipv6List, ep.addrv6.IP)
	}
	iptables.DeleteConntrackEntries(nlh, ipv4List, ipv6List)
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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.

//go:generate binapi-generator --input-dir=bin_api --output-dir=bin_api

package core

import (
	"errors"
	"os"
	"sync"
	"sync/atomic"
	"time"

	logger "github.com/Sirupsen/logrus"

	"git.fd.io/govpp.git/adapter"
	"git.fd.io/govpp.git/api"
	"git.fd.io/govpp.git/core/bin_api/vpe"
	"github.com/fsnotify/fsnotify"
	"github.com/namsral/flag"
)

const (
	requestChannelBufSize      = 100 // default size of the request channel buffers
	replyChannelBufSize        = 100 // default size of the reply channel buffers
	notificationChannelBufSize = 100 // default size of the notification channel buffers
)

var (
	// Configurable delay between VPP readiness and actual connection
	delay = flag.Int("delay", 0, "VPP connection delay time in [ms].")
)

var (
	healthCheckProbeInterval = time.Second * 1        // default health check probe interval
	healthCheckReplyTimeout  = time.Millisecond * 100 // timeout for reply to a health check probe
	healthCheckThreshold     = 1                      // number of failed healthProbe until the error is reported
)

// ConnectionState holds the current state of the connection to VPP.
type ConnectionState int

const (
	// Connected connection state means that the connection to VPP has been successfully established.
	Connected ConnectionState = iota

	// Disconnected connection state means that the connection to VPP has been lost.
	Disconnected = iota
)

const (
	// watchedFolder is a folder where vpp's shared memory is supposed to be created.
	// File system events are monitored in this folder.
	watchedFolder = "/dev/shm/"
	// watchedFile is a name of the file in the watchedFolder. Once the file is present
	// the vpp is ready to accept a new connection.
	watchedFile = watchedFolder + "vpe-api"
)

// ConnectionEvent is a notification about change in the VPP connection state.
type ConnectionEvent struct {
	// Timestamp holds the time when the event has been generated.
	Timestamp time.Time

	// State holds the new state of the connection to VPP at the time when the event has been generated.
	State ConnectionState
}

// Connection represents a shared memory connection to VPP via vppAdapter.
type Connection struct {
	vpp       adapter.VppAdapter // VPP adapter
	connected uint32             // non-zero if the adapter is connected to VPP
	codec     *MsgCodec          // message codec

	msgIDs     map[string]uint16 // map of message IDs indexed by message name + CRC
	msgIDsLock sync.RWMutex      // lock for the message IDs map

	channels     map[uint32]*api.Channel // map of all API channels indexed by the channel ID
	channelsLock sync.RWMutex            // lock for the channels map

	notifSubscriptions     map[uint16][]*api.NotifSubscription // map od all notification subscriptions indexed by message ID
	notifSubscriptionsLock sync.RWMutex                        // lock for the subscriptions map

	maxChannelID uint32 // maximum used client ID
	pingReqID    uint16 // ID if the ControlPing message
	pingReplyID  uint16 // ID of the ControlPingReply message
}

// channelMetadata contains core-local metadata of an API channel.
type channelMetadata struct {
	id        uint32 // channel ID
	multipart uint32 // 1 if multipart request is being processed, 0 otherwise
}

var (
	log      *logger.Logger // global logger
	conn     *Connection    // global handle to the Connection (used in the message receive callback)
	connLock sync.RWMutex   // lock for the global connection
)

// init initializes global logger, which logs debug level messages to stdout.
func init() {
	log = logger.New()
	log.Out = os.Stdout
	log.Level = logger.DebugLevel
}

// SetLogger sets global logger to provided one.
func SetLogger(l *logger.Logger) {
	log = l
}

// SetHealthCheckProbeInterval sets health check probe interval.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckProbeInterval(interval time.Duration) {
	healthCheckProbeInterval = interval
}

// SetHealthCheckReplyTimeout sets timeout for reply to a health check probe.
// If reply arrives after the timeout, check is considered as failed.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckReplyTimeout(timeout time.Duration) {
	healthCheckReplyTimeout = timeout
}

// SetHealthCheckThreshold sets the number of failed healthProbe checks until the error is reported.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckThreshold(threshold int) {
	healthCheckThreshold = threshold
}

// Connect connects to VPP using specified VPP adapter and returns the connection handle.
// This call blocks until VPP is connected, or an error occurs. Only one connection attempt will be performed.
func Connect(vppAdapter adapter.VppAdapter) (*Connection, error) {
	// create new connection handle
	c, err := newConnection(vppAdapter)
	if err != nil {
		return nil, err
	}

	// blocking attempt to connect to VPP
	err = c.connectVPP()
	if err != nil {
		return nil, err
	}

	return conn, nil
}

// AsyncConnect asynchronously connects to VPP using specified VPP adapter and returns the connection handle
// and ConnectionState channel. This call does not block until connection is established, it
// returns immediately. The caller is supposed to watch the returned ConnectionState channel for
// Connected/Disconnected events. In case of disconnect, the library will asynchronously try to reconnect.
func AsyncConnect(vppAdapter adapter.VppAdapter) (*Connection, chan ConnectionEvent, error) {
	// create new connection handle
	c, err := newConnection(vppAdapter)
	if err != nil {
		return nil, nil, err
	}

	// asynchronously attempt to connect to VPP
	connChan := make(chan ConnectionEvent, notificationChannelBufSize)
	go c.connectLoop(connChan)

	return conn, connChan, nil
}

// Disconnect disconnects from VPP and releases all connection-related resources.
func (c *Connection) Disconnect() {
	if c == nil {
		return
	}
	connLock.Lock()
	defer connLock.Unlock()

	if c != nil && c.vpp != nil {
		c.disconnectVPP()
	}
	conn = nil
}

// newConnection returns new connection handle.
func newConnection(vppAdapter adapter.VppAdapter) (*Connection, error) {
	connLock.Lock()
	defer connLock.Unlock()

	if conn != nil {
		return nil, errors.New("only one connection per process is supported")
	}

	conn = &Connection{vpp: vppAdapter, codec: &MsgCodec{}}
	conn.channels = make(map[uint32]*api.Channel)
	conn.msgIDs = make(map[string]uint16)
	conn.notifSubscriptions = make(map[uint16][]*api.NotifSubscription)

	conn.vpp.SetMsgCallback(msgCallback)
	return conn, nil
}

// connectVPP performs one blocking attempt to connect to VPP.
func (c *Connection) connectVPP() error {
	log.Debug("Connecting to VPP...")

	// blocking connect
	err := c.vpp.Connect()
	if err != nil {
		log.Warn(err)
		return err
	}

	// store connected state
	atomic.StoreUint32(&c.connected, 1)

	// store control ping IDs
	c.pingReqID, _ = c.GetMessageID(&vpe.ControlPing{})
	c.pingReplyID, _ = c.GetMessageID(&vpe.ControlPingReply{})

	log.Info("Connected to VPP.")
	return nil
}

// disconnectVPP disconnects from VPP in case it is connected.
func (c *Connection) disconnectVPP() {
	if atomic.CompareAndSwapUint32(&c.connected, 1, 0) {
		c.vpp.Disconnect()
	}
}

func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// waitForVpp blocks until shared memory for sending bin api calls
// is present on the file system.
func waitForVpp() error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer watcher.Close()

	err = watcher.Add(watchedFolder)
	if err != nil {
		return err
	}

	if fileExists(watchedFile) {
		return nil
	}

	for {
		ev := <-watcher.Events
		if ev.Name == watchedFile && (ev.Op&fsnotify.Create) == fsnotify.Create {
			break
		}
	}
	return nil
}

// connectLoop attempts to connect to VPP until it succeeds.
// Then it continues with healthCheckLoop.
func (c *Connection) connectLoop(connChan chan ConnectionEvent) {
	// loop until connected
	for {
		waitForVpp()
		// Delay after watched file was crated
		log.Infof("Sleeping %v [ms] while VPP will be ready", *delay)
		time.Sleep(time.Duration(*delay) * time.Millisecond)
		log.Info("VPP is ready to connect")
		err := c.connectVPP()
		if err == nil {
			// signal connected event
			connChan <- ConnectionEvent{Timestamp: time.Now(), State: Connected}
			break
		}
	}

	// we are now connected, continue with health check loop
	c.healthCheckLoop(connChan)
}

// healthCheckLoop checks whether connection to VPP is alive. In case of disconnect,
// it continues with connectLoop and tries to reconnect.
func (c *Connection) healthCheckLoop(connChan chan ConnectionEvent) {
	// create a separate API channel for health check probes
	ch, err := conn.NewAPIChannel()
	if err != nil {
		log.Error("Error by creating health check API channel, health check will be disabled:", err)
		return
	}

	failedChecks := 0
	// send health check probes until an error occurs
	for {
		// wait for healthCheckProbeInterval
		<-time.After(healthCheckProbeInterval)

		if atomic.LoadUint32(&c.connected) == 0 {
			// Disconnect has been called in the meantime, return the healthcheck - reconnect loop
			log.Debug("Disconnected on request, exiting health check loop.")
			return
		}

		// send the control ping
		ch.ReqChan <- &api.VppRequest{Message: &vpe.ControlPing{}}

		// expect response within timeout period
		select {
		case vppReply := <-ch.ReplyChan:
			err = vppReply.Error
		case <-time.After(healthCheckReplyTimeout):
			err = errors.New("probe reply not received within the timeout period")
		}

		if err != nil {
			failedChecks++
		} else {
			failedChecks = 0
		}

		if failedChecks >= healthCheckThreshold {
			// in case of error, break & disconnect
			log.Errorf("VPP health check failed: %v", err)
			// signal disconnected event via channel
			connChan <- ConnectionEvent{Timestamp: time.Now(), State: Disconnected}
			break
		}
	}

	// cleanup
	ch.Close()
	c.disconnectVPP()

	// we are now disconnected, start connect loop
	c.connectLoop(connChan)
}

// NewAPIChannel returns a new API channel for communication with VPP via govpp core.
// It uses default buffer sizes for the request and reply Go channels.
func (c *Connection) NewAPIChannel() (*api.Channel, error) {
	if c == nil {
		return nil, errors.New("nil connection passed in")
	}
	return c.NewAPIChannelBuffered(requestChannelBufSize, replyChannelBufSize)
}

// NewAPIChannelBuffered returns a new API channel for communication with VPP via govpp core.
// It allows to specify custom buffer sizes for the request and reply Go channels.
func (c *Connection) NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize int) (*api.Channel, error) {
	if c == nil {
		return nil, errors.New("nil connection passed in")
	}
	chID := atomic.AddUint32(&c.maxChannelID, 1)
	chMeta := &channelMetadata{id: chID}

	ch := api.NewChannelInternal(chMeta)
	ch.MsgDecoder = c.codec
	ch.MsgIdentifier = c

	// create the communication channels
	ch.ReqChan = make(chan *api.VppRequest, reqChanBufSize)
	ch.ReplyChan = make(chan *api.VppReply, replyChanBufSize)
	ch.NotifSubsChan = make(chan *api.NotifSubscribeRequest, reqChanBufSize)
	ch.NotifSubsReplyChan = make(chan error, replyChanBufSize)

	// store API channel within the client
	c.channelsLock.Lock()
	c.channels[chID] = ch
	c.channelsLock.Unlock()

	// start watching on the request channel
	go c.watchRequests(ch, chMeta)

	return ch, nil
}

// releaseAPIChannel releases API channel that needs to be closed.
func (c *Connection) releaseAPIChannel(ch *api.Channel, chMeta *channelMetadata) {
	log.WithFields(logger.Fields{
		"context": chMeta.id,
	}).Debug("API channel closed.")

	// delete the channel from channels map
	c.channelsLock.Lock()
	delete(c.channels, chMeta.id)
	c.channelsLock.Unlock()
}
<commit_msg>revert govpp changes (from master)<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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.

//go:generate binapi-generator --input-dir=bin_api --output-dir=bin_api

package core

import (
	"errors"
	"os"
	"sync"
	"sync/atomic"
	"time"

	logger "github.com/Sirupsen/logrus"

	"git.fd.io/govpp.git/adapter"
	"git.fd.io/govpp.git/api"
	"git.fd.io/govpp.git/core/bin_api/vpe"
	"github.com/fsnotify/fsnotify"
)

const (
	requestChannelBufSize      = 100 // default size of the request channel buffers
	replyChannelBufSize        = 100 // default size of the reply channel buffers
	notificationChannelBufSize = 100 // default size of the notification channel buffers
)

var (
	healthCheckProbeInterval = time.Second * 1        // default health check probe interval
	healthCheckReplyTimeout  = time.Millisecond * 100 // timeout for reply to a health check probe
	healthCheckThreshold     = 1                      // number of failed healthProbe until the error is reported
)

// ConnectionState holds the current state of the connection to VPP.
type ConnectionState int

const (
	// Connected connection state means that the connection to VPP has been successfully established.
	Connected ConnectionState = iota

	// Disconnected connection state means that the connection to VPP has been lost.
	Disconnected = iota
)

const (
	// watchedFolder is a folder where vpp's shared memory is supposed to be created.
	// File system events are monitored in this folder.
	watchedFolder = "/dev/shm/"
	// watchedFile is a name of the file in the watchedFolder. Once the file is present
	// the vpp is ready to accept a new connection.
	watchedFile = watchedFolder + "vpe-api"
)

// ConnectionEvent is a notification about change in the VPP connection state.
type ConnectionEvent struct {
	// Timestamp holds the time when the event has been generated.
	Timestamp time.Time

	// State holds the new state of the connection to VPP at the time when the event has been generated.
	State ConnectionState
}

// Connection represents a shared memory connection to VPP via vppAdapter.
type Connection struct {
	vpp       adapter.VppAdapter // VPP adapter
	connected uint32             // non-zero if the adapter is connected to VPP
	codec     *MsgCodec          // message codec

	msgIDs     map[string]uint16 // map of message IDs indexed by message name + CRC
	msgIDsLock sync.RWMutex      // lock for the message IDs map

	channels     map[uint32]*api.Channel // map of all API channels indexed by the channel ID
	channelsLock sync.RWMutex            // lock for the channels map

	notifSubscriptions     map[uint16][]*api.NotifSubscription // map od all notification subscriptions indexed by message ID
	notifSubscriptionsLock sync.RWMutex                        // lock for the subscriptions map

	maxChannelID uint32 // maximum used client ID
	pingReqID    uint16 // ID if the ControlPing message
	pingReplyID  uint16 // ID of the ControlPingReply message
}

// channelMetadata contains core-local metadata of an API channel.
type channelMetadata struct {
	id        uint32 // channel ID
	multipart uint32 // 1 if multipart request is being processed, 0 otherwise
}

var (
	log      *logger.Logger // global logger
	conn     *Connection    // global handle to the Connection (used in the message receive callback)
	connLock sync.RWMutex   // lock for the global connection
)

// init initializes global logger, which logs debug level messages to stdout.
func init() {
	log = logger.New()
	log.Out = os.Stdout
	log.Level = logger.DebugLevel
}

// SetLogger sets global logger to provided one.
func SetLogger(l *logger.Logger) {
	log = l
}

// SetHealthCheckProbeInterval sets health check probe interval.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckProbeInterval(interval time.Duration) {
	healthCheckProbeInterval = interval
}

// SetHealthCheckReplyTimeout sets timeout for reply to a health check probe.
// If reply arrives after the timeout, check is considered as failed.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckReplyTimeout(timeout time.Duration) {
	healthCheckReplyTimeout = timeout
}

// SetHealthCheckThreshold sets the number of failed healthProbe checks until the error is reported.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckThreshold(threshold int) {
	healthCheckThreshold = threshold
}

// Connect connects to VPP using specified VPP adapter and returns the connection handle.
// This call blocks until VPP is connected, or an error occurs. Only one connection attempt will be performed.
func Connect(vppAdapter adapter.VppAdapter) (*Connection, error) {
	// create new connection handle
	c, err := newConnection(vppAdapter)
	if err != nil {
		return nil, err
	}

	// blocking attempt to connect to VPP
	err = c.connectVPP()
	if err != nil {
		return nil, err
	}

	return conn, nil
}

// AsyncConnect asynchronously connects to VPP using specified VPP adapter and returns the connection handle
// and ConnectionState channel. This call does not block until connection is established, it
// returns immediately. The caller is supposed to watch the returned ConnectionState channel for
// Connected/Disconnected events. In case of disconnect, the library will asynchronously try to reconnect.
func AsyncConnect(vppAdapter adapter.VppAdapter) (*Connection, chan ConnectionEvent, error) {
	// create new connection handle
	c, err := newConnection(vppAdapter)
	if err != nil {
		return nil, nil, err
	}

	// asynchronously attempt to connect to VPP
	connChan := make(chan ConnectionEvent, notificationChannelBufSize)
	go c.connectLoop(connChan)

	return conn, connChan, nil
}

// Disconnect disconnects from VPP and releases all connection-related resources.
func (c *Connection) Disconnect() {
	if c == nil {
		return
	}
	connLock.Lock()
	defer connLock.Unlock()

	if c != nil && c.vpp != nil {
		c.disconnectVPP()
	}
	conn = nil
}

// newConnection returns new connection handle.
func newConnection(vppAdapter adapter.VppAdapter) (*Connection, error) {
	connLock.Lock()
	defer connLock.Unlock()

	if conn != nil {
		return nil, errors.New("only one connection per process is supported")
	}

	conn = &Connection{vpp: vppAdapter, codec: &MsgCodec{}}
	conn.channels = make(map[uint32]*api.Channel)
	conn.msgIDs = make(map[string]uint16)
	conn.notifSubscriptions = make(map[uint16][]*api.NotifSubscription)

	conn.vpp.SetMsgCallback(msgCallback)
	return conn, nil
}

// connectVPP performs one blocking attempt to connect to VPP.
func (c *Connection) connectVPP() error {
	log.Debug("Connecting to VPP...")

	// blocking connect
	err := c.vpp.Connect()
	if err != nil {
		log.Warn(err)
		return err
	}

	// store connected state
	atomic.StoreUint32(&c.connected, 1)

	// store control ping IDs
	c.pingReqID, _ = c.GetMessageID(&vpe.ControlPing{})
	c.pingReplyID, _ = c.GetMessageID(&vpe.ControlPingReply{})

	log.Info("Connected to VPP.")
	return nil
}

// disconnectVPP disconnects from VPP in case it is connected.
func (c *Connection) disconnectVPP() {
	if atomic.CompareAndSwapUint32(&c.connected, 1, 0) {
		c.vpp.Disconnect()
	}
}

func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// waitForVpp blocks until shared memory for sending bin api calls
// is present on the file system.
func waitForVpp() error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer watcher.Close()

	err = watcher.Add(watchedFolder)
	if err != nil {
		return err
	}

	if fileExists(watchedFile) {
		return nil
	}

	for {
		ev := <-watcher.Events
		if ev.Name == watchedFile && (ev.Op&fsnotify.Create) == fsnotify.Create {
			break
		}
	}
	return nil
}

// connectLoop attempts to connect to VPP until it succeeds.
// Then it continues with healthCheckLoop.
func (c *Connection) connectLoop(connChan chan ConnectionEvent) {
	// loop until connected
	for {
		waitForVpp()
		err := c.connectVPP()
		if err == nil {
			// signal connected event
			connChan <- ConnectionEvent{Timestamp: time.Now(), State: Connected}
			break
		}
	}

	// we are now connected, continue with health check loop
	c.healthCheckLoop(connChan)
}

// healthCheckLoop checks whether connection to VPP is alive. In case of disconnect,
// it continues with connectLoop and tries to reconnect.
func (c *Connection) healthCheckLoop(connChan chan ConnectionEvent) {
	// create a separate API channel for health check probes
	ch, err := conn.NewAPIChannel()
	if err != nil {
		log.Error("Error by creating health check API channel, health check will be disabled:", err)
		return
	}

	failedChecks := 0
	// send health check probes until an error occurs
	for {
		// wait for healthCheckProbeInterval
		<-time.After(healthCheckProbeInterval)

		if atomic.LoadUint32(&c.connected) == 0 {
			// Disconnect has been called in the meantime, return the healthcheck - reconnect loop
			log.Debug("Disconnected on request, exiting health check loop.")
			return
		}

		// send the control ping
		ch.ReqChan <- &api.VppRequest{Message: &vpe.ControlPing{}}

		// expect response within timeout period
		select {
		case vppReply := <-ch.ReplyChan:
			err = vppReply.Error
		case <-time.After(healthCheckReplyTimeout):
			err = errors.New("probe reply not received within the timeout period")
		}

		if err != nil {
			failedChecks++
		} else {
			failedChecks = 0
		}

		if failedChecks >= healthCheckThreshold {
			// in case of error, break & disconnect
			log.Errorf("VPP health check failed: %v", err)
			// signal disconnected event via channel
			connChan <- ConnectionEvent{Timestamp: time.Now(), State: Disconnected}
			break
		}
	}

	// cleanup
	ch.Close()
	c.disconnectVPP()

	// we are now disconnected, start connect loop
	c.connectLoop(connChan)
}

// NewAPIChannel returns a new API channel for communication with VPP via govpp core.
// It uses default buffer sizes for the request and reply Go channels.
func (c *Connection) NewAPIChannel() (*api.Channel, error) {
	if c == nil {
		return nil, errors.New("nil connection passed in")
	}
	return c.NewAPIChannelBuffered(requestChannelBufSize, replyChannelBufSize)
}

// NewAPIChannelBuffered returns a new API channel for communication with VPP via govpp core.
// It allows to specify custom buffer sizes for the request and reply Go channels.
func (c *Connection) NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize int) (*api.Channel, error) {
	if c == nil {
		return nil, errors.New("nil connection passed in")
	}
	chID := atomic.AddUint32(&c.maxChannelID, 1)
	chMeta := &channelMetadata{id: chID}

	ch := api.NewChannelInternal(chMeta)
	ch.MsgDecoder = c.codec
	ch.MsgIdentifier = c

	// create the communication channels
	ch.ReqChan = make(chan *api.VppRequest, reqChanBufSize)
	ch.ReplyChan = make(chan *api.VppReply, replyChanBufSize)
	ch.NotifSubsChan = make(chan *api.NotifSubscribeRequest, reqChanBufSize)
	ch.NotifSubsReplyChan = make(chan error, replyChanBufSize)

	// store API channel within the client
	c.channelsLock.Lock()
	c.channels[chID] = ch
	c.channelsLock.Unlock()

	// start watching on the request channel
	go c.watchRequests(ch, chMeta)

	return ch, nil
}

// releaseAPIChannel releases API channel that needs to be closed.
func (c *Connection) releaseAPIChannel(ch *api.Channel, chMeta *channelMetadata) {
	log.WithFields(logger.Fields{
		"context": chMeta.id,
	}).Debug("API channel closed.")

	// delete the channel from channels map
	c.channelsLock.Lock()
	delete(c.channels, chMeta.id)
	c.channelsLock.Unlock()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"unicode/utf8"

	"github.com/cubicdaiya/gonp"
)

func main() {
	if len(os.Args) < 3 {
		log.Fatal("./strdiff arg1 arg2")
	}
	if !utf8.ValidString(os.Args[1]) {
		log.Fatal("arg1 contains invalid rune")
	}

	if !utf8.ValidString(os.Args[2]) {
		log.Fatal("arg2 contains invalid rune")
	}
	a := []rune(os.Args[1])
	b := []rune(os.Args[2])
	diff := gonp.New(a, b)
	diff.Compose()

	patchedSeq := diff.Patch(a)
	fmt.Printf("success:%v, applying SES between '%s' and '%s' to '%s' is '%s'\n",
		string(b) == string(patchedSeq),
		string(a), string(b),
		string(a), string(patchedSeq))

	uniPatchedSeq, err := diff.UniPatch(a, diff.UnifiedHunks())
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("success:%v, applying unified format difference between '%s' and '%s' to '%s' is '%s'\n",
		string(b) == string(uniPatchedSeq),
		string(a), string(b),
		string(a), string(uniPatchedSeq))
}
<commit_msg>examples: fixed wrong progname in error output.<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"unicode/utf8"

	"github.com/cubicdaiya/gonp"
)

func main() {
	if len(os.Args) < 3 {
		log.Fatal("./strpatch arg1 arg2")
	}
	if !utf8.ValidString(os.Args[1]) {
		log.Fatal("arg1 contains invalid rune")
	}

	if !utf8.ValidString(os.Args[2]) {
		log.Fatal("arg2 contains invalid rune")
	}
	a := []rune(os.Args[1])
	b := []rune(os.Args[2])
	diff := gonp.New(a, b)
	diff.Compose()

	patchedSeq := diff.Patch(a)
	fmt.Printf("success:%v, applying SES between '%s' and '%s' to '%s' is '%s'\n",
		string(b) == string(patchedSeq),
		string(a), string(b),
		string(a), string(patchedSeq))

	uniPatchedSeq, err := diff.UniPatch(a, diff.UnifiedHunks())
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("success:%v, applying unified format difference between '%s' and '%s' to '%s' is '%s'\n",
		string(b) == string(uniPatchedSeq),
		string(a), string(b),
		string(a), string(uniPatchedSeq))
}
<|endoftext|>


<commit_before>package exchange

import (
	"fmt"
	"github.com/bkrem/blockchain.info-api-v1-client-go/api"
	"github.com/bkrem/blockchain.info-api-v1-client-go/util"
)

type exchangeOpts struct {
	api.Opts
	Currency string `url:"currency"`
	Value    string `url:"value"`
	Time     int    `url:"time,omitempty"`
}

var endpoints = map[string]string{
	"ticker":  "/ticker?",
	"frombtc": "/frombtc?",
	"tobtc":   "/tobtc?",
}

var client = api.API{BaseURL: "https://blockchain.info", Endpoints: endpoints}

func GetTicker() (string, error) {
	res, err := client.Get("ticker")
	return res, err
}

// FIXME query string seems ineffectual (#1)
/*
func GetTickerForCurrency(currency string) string {
	opts := encodeOpts(exchangeOpts{Currency: currency})
	res := client.GetWithOpts("ticker", opts)
	fmt.Println(res)
	return res
}
*/

func ToBTC(amount float64, currency string) (float64, error) {
	amountStr := util.Float64ToString(amount)
	eo := exchangeOpts{Opts: api.Opts{}, Currency: currency, Value: amountStr}
	opts := client.EncodeOpts(eo)
	res, err := client.GetWithOpts("tobtc", opts)
	if err != nil {
		return 0, err
	}
	parsedRes, err := util.StringToFloat64(res)
	fmt.Println(parsedRes)
	return parsedRes, err
}

func FromBTC(amount int, currency string) (float64, error) {
	amountStr := util.IntToString(amount)
	eo := exchangeOpts{Opts: api.Opts{}, Currency: currency, Value: amountStr}
	opts := client.EncodeOpts(eo)
	res, err := client.GetWithOpts("frombtc", opts)
	if err != nil {
		return 0, err
	}
	parsedRes, err := util.StringToFloat64(res)
	fmt.Println(parsedRes)
	return parsedRes, err
}
<commit_msg>Formatting with goimports<commit_after>package exchange

import (
	"fmt"

	"github.com/bkrem/blockchain.info-api-v1-client-go/api"
	"github.com/bkrem/blockchain.info-api-v1-client-go/util"
)

type exchangeOpts struct {
	api.Opts
	Currency string `url:"currency"`
	Value    string `url:"value"`
	Time     int    `url:"time,omitempty"`
}

var endpoints = map[string]string{
	"ticker":  "/ticker?",
	"frombtc": "/frombtc?",
	"tobtc":   "/tobtc?",
}

var client = api.API{BaseURL: "https://blockchain.info", Endpoints: endpoints}

func GetTicker() (string, error) {
	res, err := client.Get("ticker")
	return res, err
}

// FIXME query string seems ineffectual (#1)
/*
func GetTickerForCurrency(currency string) string {
	opts := encodeOpts(exchangeOpts{Currency: currency})
	res := client.GetWithOpts("ticker", opts)
	fmt.Println(res)
	return res
}
*/

func ToBTC(amount float64, currency string) (float64, error) {
	amountStr := util.Float64ToString(amount)
	eo := exchangeOpts{Opts: api.Opts{}, Currency: currency, Value: amountStr}
	opts := client.EncodeOpts(eo)
	res, err := client.GetWithOpts("tobtc", opts)
	if err != nil {
		return 0, err
	}
	parsedRes, err := util.StringToFloat64(res)
	fmt.Println(parsedRes)
	return parsedRes, err
}

func FromBTC(amount int, currency string) (float64, error) {
	amountStr := util.IntToString(amount)
	eo := exchangeOpts{Opts: api.Opts{}, Currency: currency, Value: amountStr}
	opts := client.EncodeOpts(eo)
	res, err := client.GetWithOpts("frombtc", opts)
	if err != nil {
		return 0, err
	}
	parsedRes, err := util.StringToFloat64(res)
	fmt.Println(parsedRes)
	return parsedRes, err
}
<|endoftext|>


<commit_before>package api

import (
	"context"
	"errors"
	"fmt"
	"math"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/grafana/metrictank/api/middleware"
	"github.com/grafana/metrictank/api/models"
	"github.com/grafana/metrictank/api/response"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/promql"
	"github.com/prometheus/prometheus/storage"
	schema "gopkg.in/raintank/schema.v1"
)

type orgID string
type status string

const (
	statusSuccess status = "success"
	statusError   status = "error"
)

type errorType string

const (
	errorTimeout  errorType = "timeout"
	errorCanceled errorType = "canceled"
	errorExec     errorType = "execution"
	errorBadData  errorType = "bad_data"
	errorInternal errorType = "internal"
)

type prometheusQueryResult struct {
	Status    status      `json:"status"`
	Data      interface{} `json:"data,omitempty"`
	ErrorType errorType   `json:"errorType,omitempty"`
	Error     error       `json:"error,omitempty"`
}

type prometheusQueryData struct {
	ResultType promql.ValueType `json:"resultType"`
	Result     promql.Value     `json:"result"`
}

func (s *Server) prometheusLabelValues(ctx *middleware.Context) {
	name := ctx.Params(":name")

	if !model.LabelNameRE.MatchString(name) {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     errors.New("invalid name"),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	q := NewQuerier(ctx.Req.Context(), s, 0, 0, ctx.OrgId, false)

	defer q.Close()
	vals, err := q.LabelValues(name)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("query failed: %v", err),
			ErrorType: errorExec,
		}, ""))
		return
	}

	response.Write(ctx, response.NewJson(200, prometheusQueryResult{Status: "success", Data: vals}, ""))
	return
}

func (s *Server) prometheusQueryRange(ctx *middleware.Context, request models.PrometheusRangeQuery) {
	start, err := parseTime(request.Start)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("invalid start time: %v", err),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	end, err := parseTime(request.End)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("invalid end time: %v", err),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	step, err := parseDuration(request.Step)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("could not parse step duration: %v", err),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	if step <= 0 {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("step value is less than or equal to zero: %v", step),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	qry, err := s.PromQueryEngine.NewRangeQuery(request.Query, start, end, step)

	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("query failed: %v", err),
			ErrorType: errorExec,
		}, ""))
		return
	}

	newCtx := context.WithValue(ctx.Req.Context(), orgID("org-id"), ctx.OrgId)
	res := qry.Exec(newCtx)

	if res.Err != nil {
		if res.Err != nil {
			switch res.Err.(type) {
			case promql.ErrQueryCanceled:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorCanceled,
				}, ""))
			case promql.ErrQueryTimeout:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorTimeout,
				}, ""))
			case promql.ErrStorage:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorInternal,
				}, ""))
			}
			response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
				Status:    statusError,
				Error:     fmt.Errorf("query failed: %v", res.Err),
				ErrorType: errorExec,
			}, ""))
		}
		return
	}

	response.Write(ctx, response.NewJson(200,
		prometheusQueryResult{
			Data: prometheusQueryData{
				ResultType: res.Value.Type(),
				Result:     res.Value,
			},
			Status: statusSuccess,
		},
		"",
	))
}

func (s *Server) prometheusQueryInstant(ctx *middleware.Context, request models.PrometheusQueryInstant) {
	ts, err := parseTime(request.Time)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("could not parse ts time: %v", err),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	qry, err := s.PromQueryEngine.NewInstantQuery(request.Query, ts)

	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("query failed: %v", err),
			ErrorType: errorExec,
		}, ""))
		return
	}

	newCtx := context.WithValue(ctx.Req.Context(), orgID("org-id"), ctx.OrgId)
	res := qry.Exec(newCtx)

	if res.Err != nil {
		if res.Err != nil {
			switch res.Err.(type) {
			case promql.ErrQueryCanceled:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorCanceled,
				}, ""))
			case promql.ErrQueryTimeout:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorTimeout,
				}, ""))
			case promql.ErrStorage:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorInternal,
				}, ""))
			}
			response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
				Status:    statusError,
				Error:     fmt.Errorf("query failed: %v", res.Err),
				ErrorType: errorExec,
			}, ""))
		}
		return
	}

	response.Write(ctx, response.NewJson(200,
		prometheusQueryResult{
			Data: prometheusQueryData{
				ResultType: res.Value.Type(),
				Result:     res.Value,
			},
			Status: statusSuccess,
		},
		"",
	))
}

func (s *Server) prometheusQuerySeries(ctx *middleware.Context, request models.PrometheusSeriesQuery) {
	start, err := parseTime(request.Start)
	if err != nil {
		response.Write(ctx, response.NewError(http.StatusBadRequest, fmt.Sprintf("could not parse start time: %v", err)))
		return
	}

	end, err := parseTime(request.End)
	if err != nil {
		response.Write(ctx, response.NewError(http.StatusBadRequest, fmt.Sprintf("could not parse end time: %v", err)))
		return
	}

	q := NewQuerier(ctx.Req.Context(), s, uint32(start.Unix()), uint32(end.Unix()), ctx.OrgId, true)

	var matcherSets [][]*labels.Matcher
	for _, s := range request.Match {
		matchers, err := promql.ParseMetricSelector(s)
		if err != nil {
			response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
				Status:    statusError,
				Error:     fmt.Errorf("query failed: %v", err),
				ErrorType: errorBadData,
			}, ""))
			return
		}
		matcherSets = append(matcherSets, matchers)
	}

	var sets []storage.SeriesSet
	for _, mset := range matcherSets {
		s, err := q.Select(mset...)
		if err != nil {
			response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
				Status:    statusError,
				Error:     fmt.Errorf("query failed: %v", err),
				ErrorType: errorExec,
			}, ""))
			return
		}
		sets = append(sets, s)
	}

	set := storage.NewMergeSeriesSet(sets)
	metrics := []labels.Labels{}
	for set.Next() {
		metrics = append(metrics, set.At().Labels())
	}
	if set.Err() != nil {
		response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("query failed: %v", err),
			ErrorType: errorExec,
		}, ""))
		return
	}

	response.Write(ctx, response.NewJson(200,
		prometheusQueryResult{
			Data:   metrics,
			Status: statusSuccess,
		},
		"",
	))

	return
}

func parseTime(s string) (time.Time, error) {
	if t, err := strconv.ParseFloat(s, 64); err == nil {
		s, ns := math.Modf(t)
		return time.Unix(int64(s), int64(ns*float64(time.Second))), nil
	}
	if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
		return t, nil
	}
	return time.Time{}, fmt.Errorf("cannot parse %q to a valid timestamp", s)
}

func parseDuration(s string) (time.Duration, error) {
	if d, err := strconv.ParseFloat(s, 64); err == nil {
		ts := d * float64(time.Second)
		if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) {
			return 0, fmt.Errorf("cannot parse %q to a valid duration. It overflows int64", s)
		}
		return time.Duration(ts), nil
	}
	if d, err := model.ParseDuration(s); err == nil {
		return time.Duration(d), nil
	}
	return 0, fmt.Errorf("cannot parse %q to a valid duration", s)
}

func SeriesToSeriesSet(out []models.Series) (*models.PrometheusSeriesSet, error) {
	series := []storage.Series{}
	for _, metric := range out {
		series = append(series, models.NewPrometheusSeries(buildTagSet(metric.Target), dataPointsToPrometheusSamplePairs(metric.Datapoints)))
	}
	return models.NewPrometheusSeriesSet(series), nil
}

func dataPointsToPrometheusSamplePairs(data []schema.Point) []model.SamplePair {
	samples := []model.SamplePair{}
	for _, point := range data {
		if math.IsNaN(point.Val) {
			continue
		}
		samples = append(samples, model.SamplePair{
			Timestamp: model.Time(int64(point.Ts) * 1000),
			Value:     model.SampleValue(point.Val),
		})
	}
	return samples
}

func BuildMetadataSeriesSet(seriesNames []Series) (*models.PrometheusSeriesSet, error) {
	series := []storage.Series{}
	for _, s := range seriesNames {
		for _, metric := range s.Series {
			for _, archive := range metric.Defs {
				series = append(series, models.NewPrometheusSeries(buildTagSet(archive.NameWithTags()), []model.SamplePair{}))
			}
		}
	}
	return models.NewPrometheusSeriesSet(series), nil
}

// Turns graphite target name into prometheus graphite name
// TODO models.Series should provide a map of tags but the one returned from getTargets doesn't
func buildTagSet(name string) map[string]string {
	labelMap := map[string]string{}
	tags := strings.Split(name, ";")
	labelMap["__name__"] = tags[0]
	for _, lbl := range tags[1:] {
		kv := strings.Split(lbl, "=")
		labelMap[kv[0]] = kv[1]
	}
	return labelMap
}
<commit_msg>performance tweak<commit_after>package api

import (
	"context"
	"errors"
	"fmt"
	"math"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/grafana/metrictank/api/middleware"
	"github.com/grafana/metrictank/api/models"
	"github.com/grafana/metrictank/api/response"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/pkg/labels"
	"github.com/prometheus/prometheus/promql"
	"github.com/prometheus/prometheus/storage"
	schema "gopkg.in/raintank/schema.v1"
)

type orgID string
type status string

const (
	statusSuccess status = "success"
	statusError   status = "error"
)

type errorType string

const (
	errorTimeout  errorType = "timeout"
	errorCanceled errorType = "canceled"
	errorExec     errorType = "execution"
	errorBadData  errorType = "bad_data"
	errorInternal errorType = "internal"
)

type prometheusQueryResult struct {
	Status    status      `json:"status"`
	Data      interface{} `json:"data,omitempty"`
	ErrorType errorType   `json:"errorType,omitempty"`
	Error     error       `json:"error,omitempty"`
}

type prometheusQueryData struct {
	ResultType promql.ValueType `json:"resultType"`
	Result     promql.Value     `json:"result"`
}

func (s *Server) prometheusLabelValues(ctx *middleware.Context) {
	name := ctx.Params(":name")

	if !model.LabelName(name).IsValid() {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     errors.New("invalid name"),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	q := NewQuerier(ctx.Req.Context(), s, 0, 0, ctx.OrgId, false)

	defer q.Close()
	vals, err := q.LabelValues(name)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("query failed: %v", err),
			ErrorType: errorExec,
		}, ""))
		return
	}

	response.Write(ctx, response.NewJson(200, prometheusQueryResult{Status: "success", Data: vals}, ""))
	return
}

func (s *Server) prometheusQueryRange(ctx *middleware.Context, request models.PrometheusRangeQuery) {
	start, err := parseTime(request.Start)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("invalid start time: %v", err),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	end, err := parseTime(request.End)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("invalid end time: %v", err),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	step, err := parseDuration(request.Step)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("could not parse step duration: %v", err),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	if step <= 0 {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("step value is less than or equal to zero: %v", step),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	qry, err := s.PromQueryEngine.NewRangeQuery(request.Query, start, end, step)

	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("query failed: %v", err),
			ErrorType: errorExec,
		}, ""))
		return
	}

	newCtx := context.WithValue(ctx.Req.Context(), orgID("org-id"), ctx.OrgId)
	res := qry.Exec(newCtx)

	if res.Err != nil {
		if res.Err != nil {
			switch res.Err.(type) {
			case promql.ErrQueryCanceled:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorCanceled,
				}, ""))
			case promql.ErrQueryTimeout:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorTimeout,
				}, ""))
			case promql.ErrStorage:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorInternal,
				}, ""))
			}
			response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
				Status:    statusError,
				Error:     fmt.Errorf("query failed: %v", res.Err),
				ErrorType: errorExec,
			}, ""))
		}
		return
	}

	response.Write(ctx, response.NewJson(200,
		prometheusQueryResult{
			Data: prometheusQueryData{
				ResultType: res.Value.Type(),
				Result:     res.Value,
			},
			Status: statusSuccess,
		},
		"",
	))
}

func (s *Server) prometheusQueryInstant(ctx *middleware.Context, request models.PrometheusQueryInstant) {
	ts, err := parseTime(request.Time)
	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("could not parse ts time: %v", err),
			ErrorType: errorBadData,
		}, ""))
		return
	}

	qry, err := s.PromQueryEngine.NewInstantQuery(request.Query, ts)

	if err != nil {
		response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("query failed: %v", err),
			ErrorType: errorExec,
		}, ""))
		return
	}

	newCtx := context.WithValue(ctx.Req.Context(), orgID("org-id"), ctx.OrgId)
	res := qry.Exec(newCtx)

	if res.Err != nil {
		if res.Err != nil {
			switch res.Err.(type) {
			case promql.ErrQueryCanceled:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorCanceled,
				}, ""))
			case promql.ErrQueryTimeout:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorTimeout,
				}, ""))
			case promql.ErrStorage:
				response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
					Status:    statusError,
					Error:     fmt.Errorf("query failed: %v", res.Err),
					ErrorType: errorInternal,
				}, ""))
			}
			response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
				Status:    statusError,
				Error:     fmt.Errorf("query failed: %v", res.Err),
				ErrorType: errorExec,
			}, ""))
		}
		return
	}

	response.Write(ctx, response.NewJson(200,
		prometheusQueryResult{
			Data: prometheusQueryData{
				ResultType: res.Value.Type(),
				Result:     res.Value,
			},
			Status: statusSuccess,
		},
		"",
	))
}

func (s *Server) prometheusQuerySeries(ctx *middleware.Context, request models.PrometheusSeriesQuery) {
	start, err := parseTime(request.Start)
	if err != nil {
		response.Write(ctx, response.NewError(http.StatusBadRequest, fmt.Sprintf("could not parse start time: %v", err)))
		return
	}

	end, err := parseTime(request.End)
	if err != nil {
		response.Write(ctx, response.NewError(http.StatusBadRequest, fmt.Sprintf("could not parse end time: %v", err)))
		return
	}

	q := NewQuerier(ctx.Req.Context(), s, uint32(start.Unix()), uint32(end.Unix()), ctx.OrgId, true)

	var matcherSets [][]*labels.Matcher
	for _, s := range request.Match {
		matchers, err := promql.ParseMetricSelector(s)
		if err != nil {
			response.Write(ctx, response.NewJson(http.StatusBadRequest, prometheusQueryResult{
				Status:    statusError,
				Error:     fmt.Errorf("query failed: %v", err),
				ErrorType: errorBadData,
			}, ""))
			return
		}
		matcherSets = append(matcherSets, matchers)
	}

	var sets []storage.SeriesSet
	for _, mset := range matcherSets {
		s, err := q.Select(mset...)
		if err != nil {
			response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
				Status:    statusError,
				Error:     fmt.Errorf("query failed: %v", err),
				ErrorType: errorExec,
			}, ""))
			return
		}
		sets = append(sets, s)
	}

	set := storage.NewMergeSeriesSet(sets)
	metrics := []labels.Labels{}
	for set.Next() {
		metrics = append(metrics, set.At().Labels())
	}
	if set.Err() != nil {
		response.Write(ctx, response.NewJson(http.StatusInternalServerError, prometheusQueryResult{
			Status:    statusError,
			Error:     fmt.Errorf("query failed: %v", err),
			ErrorType: errorExec,
		}, ""))
		return
	}

	response.Write(ctx, response.NewJson(200,
		prometheusQueryResult{
			Data:   metrics,
			Status: statusSuccess,
		},
		"",
	))

	return
}

func parseTime(s string) (time.Time, error) {
	if t, err := strconv.ParseFloat(s, 64); err == nil {
		s, ns := math.Modf(t)
		return time.Unix(int64(s), int64(ns*float64(time.Second))), nil
	}
	if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
		return t, nil
	}
	return time.Time{}, fmt.Errorf("cannot parse %q to a valid timestamp", s)
}

func parseDuration(s string) (time.Duration, error) {
	if d, err := strconv.ParseFloat(s, 64); err == nil {
		ts := d * float64(time.Second)
		if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) {
			return 0, fmt.Errorf("cannot parse %q to a valid duration. It overflows int64", s)
		}
		return time.Duration(ts), nil
	}
	if d, err := model.ParseDuration(s); err == nil {
		return time.Duration(d), nil
	}
	return 0, fmt.Errorf("cannot parse %q to a valid duration", s)
}

func SeriesToSeriesSet(out []models.Series) (*models.PrometheusSeriesSet, error) {
	series := []storage.Series{}
	for _, metric := range out {
		series = append(series, models.NewPrometheusSeries(buildTagSet(metric.Target), dataPointsToPrometheusSamplePairs(metric.Datapoints)))
	}
	return models.NewPrometheusSeriesSet(series), nil
}

func dataPointsToPrometheusSamplePairs(data []schema.Point) []model.SamplePair {
	samples := []model.SamplePair{}
	for _, point := range data {
		if math.IsNaN(point.Val) {
			continue
		}
		samples = append(samples, model.SamplePair{
			Timestamp: model.Time(int64(point.Ts) * 1000),
			Value:     model.SampleValue(point.Val),
		})
	}
	return samples
}

func BuildMetadataSeriesSet(seriesNames []Series) (*models.PrometheusSeriesSet, error) {
	series := []storage.Series{}
	for _, s := range seriesNames {
		for _, metric := range s.Series {
			for _, archive := range metric.Defs {
				series = append(series, models.NewPrometheusSeries(buildTagSet(archive.NameWithTags()), []model.SamplePair{}))
			}
		}
	}
	return models.NewPrometheusSeriesSet(series), nil
}

// Turns graphite target name into prometheus graphite name
// TODO models.Series should provide a map of tags but the one returned from getTargets doesn't
func buildTagSet(name string) map[string]string {
	labelMap := map[string]string{}
	tags := strings.Split(name, ";")
	labelMap["__name__"] = tags[0]
	for _, lbl := range tags[1:] {
		kv := strings.Split(lbl, "=")
		labelMap[kv[0]] = kv[1]
	}
	return labelMap
}
<|endoftext|>


<commit_before>//go:build linux
// +build linux

/*
   Copyright The containerd 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 devmapper

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/containerd/containerd/mount"
	"github.com/containerd/containerd/pkg/testutil"
	"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
	"github.com/docker/go-units"
	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	exec "golang.org/x/sys/execabs"
)

const (
	thinDevice1 = "thin-1"
	thinDevice2 = "thin-2"
	snapDevice1 = "snap-1"
	device1Size = 100000
	device2Size = 200000
	testsPrefix = "devmapper-snapshotter-tests-"
)

// TestPoolDevice runs integration tests for pool device.
// The following scenario implemented:
// - Create pool device with name 'test-pool-device'
// - Create two thin volumes 'thin-1' and 'thin-2'
// - Write ext4 file system on 'thin-1' and make sure it'errs moutable
// - Write v1 test file on 'thin-1' volume
// - Take 'thin-1' snapshot 'snap-1'
// - Change v1 file to v2 on 'thin-1'
// - Mount 'snap-1' and make sure test file is v1
// - Unmount volumes and remove all devices
func TestPoolDevice(t *testing.T) {
	testutil.RequiresRoot(t)

	logrus.SetLevel(logrus.DebugLevel)
	ctx := context.Background()

	tempDir := t.TempDir()

	_, loopDataDevice := createLoopbackDevice(t, tempDir)
	_, loopMetaDevice := createLoopbackDevice(t, tempDir)

	poolName := fmt.Sprintf("test-pool-device-%d", time.Now().Nanosecond())
	err := dmsetup.CreatePool(poolName, loopDataDevice, loopMetaDevice, 64*1024/dmsetup.SectorSize)
	assert.Nil(t, err, "failed to create pool %q", poolName)

	defer func() {
		// Detach loop devices and remove images
		err := mount.DetachLoopDevice(loopDataDevice, loopMetaDevice)
		assert.NoError(t, err)
	}()

	config := &Config{
		PoolName:           poolName,
		RootPath:           tempDir,
		BaseImageSize:      "16mb",
		BaseImageSizeBytes: 16 * 1024 * 1024,
		DiscardBlocks:      true,
	}

	pool, err := NewPoolDevice(ctx, config)
	assert.Nil(t, err, "can't create device pool")
	assert.True(t, pool != nil)

	defer func() {
		err := pool.RemovePool(ctx)
		assert.Nil(t, err, "can't close device pool")
	}()

	// Create thin devices
	t.Run("CreateThinDevice", func(t *testing.T) {
		testCreateThinDevice(t, pool)
	})

	// Make ext4 filesystem on 'thin-1'
	t.Run("MakeFileSystem", func(t *testing.T) {
		testMakeFileSystem(t, pool)
	})

	// Mount 'thin-1' and write v1 test file on 'thin-1' device
	err = mount.WithTempMount(ctx, getMounts(thinDevice1), func(thin1MountPath string) error {
		// Write v1 test file on 'thin-1' device
		thin1TestFilePath := filepath.Join(thin1MountPath, "TEST")
		err := os.WriteFile(thin1TestFilePath, []byte("test file (v1)"), 0700)
		assert.Nil(t, err, "failed to write test file v1 on '%s' volume", thinDevice1)

		return nil
	})

	// Take snapshot of 'thin-1'
	t.Run("CreateSnapshotDevice", func(t *testing.T) {
		testCreateSnapshot(t, pool)
	})

	// Update TEST file on 'thin-1' to v2
	err = mount.WithTempMount(ctx, getMounts(thinDevice1), func(thin1MountPath string) error {
		thin1TestFilePath := filepath.Join(thin1MountPath, "TEST")
		err = os.WriteFile(thin1TestFilePath, []byte("test file (v2)"), 0700)
		assert.Nil(t, err, "failed to write test file v2 on 'thin-1' volume after taking snapshot")

		return nil
	})

	assert.NoError(t, err)

	// Mount 'snap-1' and make sure TEST file is v1
	err = mount.WithTempMount(ctx, getMounts(snapDevice1), func(snap1MountPath string) error {
		// Read test file from snapshot device and make sure it's v1
		fileData, err := os.ReadFile(filepath.Join(snap1MountPath, "TEST"))
		assert.Nil(t, err, "couldn't read test file from '%s' device", snapDevice1)
		assert.Equal(t, "test file (v1)", string(fileData), "test file content is invalid on snapshot")

		return nil
	})

	assert.NoError(t, err)

	t.Run("DeactivateDevice", func(t *testing.T) {
		testDeactivateThinDevice(t, pool)
	})

	t.Run("RemoveDevice", func(t *testing.T) {
		testRemoveThinDevice(t, pool)
	})

	t.Run("rollbackActivate", func(t *testing.T) {
		testCreateThinDevice(t, pool)

		ctx := context.Background()

		snapDevice := "snap2"

		err := pool.CreateSnapshotDevice(ctx, thinDevice1, snapDevice, device1Size)
		assert.NoError(t, err)

		info, err := pool.metadata.GetDevice(ctx, snapDevice)
		assert.NoError(t, err)

		// Simulate a case that the device cannot be activated.
		err = pool.DeactivateDevice(ctx, info.Name, false, false)
		assert.NoError(t, err)

		err = pool.rollbackActivate(ctx, info, err)
		assert.NoError(t, err)
	})
}

func TestPoolDeviceMarkFaulty(t *testing.T) {
	store := createStore(t)
	defer cleanupStore(t, store)

	err := store.AddDevice(testCtx, &DeviceInfo{Name: "1", State: Unknown})
	assert.NoError(t, err)

	// Note: do not use 'Activated' here because pool.ensureDeviceStates() will
	// try to activate the real dm device, which will fail on a faked device.
	err = store.AddDevice(testCtx, &DeviceInfo{Name: "2", State: Deactivated})
	assert.NoError(t, err)

	pool := &PoolDevice{metadata: store}
	err = pool.ensureDeviceStates(testCtx)
	assert.NoError(t, err)

	called := 0
	err = pool.metadata.WalkDevices(testCtx, func(info *DeviceInfo) error {
		called++

		switch called {
		case 1:
			assert.Equal(t, Faulty, info.State)
			assert.Equal(t, "1", info.Name)
		case 2:
			assert.Equal(t, Deactivated, info.State)
			assert.Equal(t, "2", info.Name)
		default:
			t.Error("unexpected walk call")
		}

		return nil
	})
	assert.NoError(t, err)
	assert.Equal(t, 2, called)
}

func testCreateThinDevice(t *testing.T, pool *PoolDevice) {
	ctx := context.Background()

	err := pool.CreateThinDevice(ctx, thinDevice1, device1Size)
	assert.Nil(t, err, "can't create first thin device")

	err = pool.CreateThinDevice(ctx, thinDevice1, device1Size)
	assert.True(t, err != nil, "device pool allows duplicated device names")

	err = pool.CreateThinDevice(ctx, thinDevice2, device2Size)
	assert.Nil(t, err, "can't create second thin device")

	deviceInfo1, err := pool.metadata.GetDevice(ctx, thinDevice1)
	assert.NoError(t, err)

	deviceInfo2, err := pool.metadata.GetDevice(ctx, thinDevice2)
	assert.NoError(t, err)

	assert.True(t, deviceInfo1.DeviceID != deviceInfo2.DeviceID, "assigned device ids should be different")

	usage, err := pool.GetUsage(thinDevice1)
	assert.NoError(t, err)
	assert.Equal(t, usage, int64(0))
}

func testMakeFileSystem(t *testing.T, pool *PoolDevice) {
	devicePath := dmsetup.GetFullDevicePath(thinDevice1)
	args := []string{
		devicePath,
		"-E",
		"nodiscard,lazy_itable_init=0,lazy_journal_init=0",
	}

	output, err := exec.Command("mkfs.ext4", args...).CombinedOutput()
	assert.Nil(t, err, "failed to make filesystem on '%s': %s", thinDevice1, string(output))

	usage, err := pool.GetUsage(thinDevice1)
	assert.NoError(t, err)
	assert.True(t, usage > 0)
}

func testCreateSnapshot(t *testing.T, pool *PoolDevice) {
	err := pool.CreateSnapshotDevice(context.Background(), thinDevice1, snapDevice1, device1Size)
	assert.Nil(t, err, "failed to create snapshot from '%s' volume", thinDevice1)
}

func testDeactivateThinDevice(t *testing.T, pool *PoolDevice) {
	deviceList := []string{
		thinDevice2,
		snapDevice1,
	}

	for _, deviceName := range deviceList {
		assert.True(t, pool.IsActivated(deviceName))

		err := pool.DeactivateDevice(context.Background(), deviceName, false, true)
		assert.Nil(t, err, "failed to remove '%s'", deviceName)

		assert.False(t, pool.IsActivated(deviceName))
	}
}

func testRemoveThinDevice(t *testing.T, pool *PoolDevice) {
	err := pool.RemoveDevice(testCtx, thinDevice1)
	assert.Nil(t, err, "should delete thin device from pool")

	err = pool.RemoveDevice(testCtx, thinDevice2)
	assert.Nil(t, err, "should delete thin device from pool")
}

func getMounts(thinDeviceName string) []mount.Mount {
	return []mount.Mount{
		{
			Source: dmsetup.GetFullDevicePath(thinDeviceName),
			Type:   "ext4",
		},
	}
}

func createLoopbackDevice(t *testing.T, dir string) (string, string) {
	file, err := os.CreateTemp(dir, testsPrefix)
	assert.NoError(t, err)

	size, err := units.RAMInBytes("128Mb")
	assert.NoError(t, err)

	err = file.Truncate(size)
	assert.NoError(t, err)

	err = file.Close()
	assert.NoError(t, err)

	imagePath := file.Name()

	loopDevice, err := mount.AttachLoopDevice(imagePath)
	assert.NoError(t, err)

	return imagePath, loopDevice
}
<commit_msg>fix pool_device_test.go<commit_after>//go:build linux
// +build linux

/*
   Copyright The containerd 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 devmapper

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/containerd/containerd/mount"
	"github.com/containerd/containerd/pkg/testutil"
	"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
	"github.com/docker/go-units"
	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	exec "golang.org/x/sys/execabs"
)

const (
	thinDevice1 = "thin-1"
	thinDevice2 = "thin-2"
	snapDevice1 = "snap-1"
	device1Size = 1000000
	device2Size = 2000000
	testsPrefix = "devmapper-snapshotter-tests-"
)

// TestPoolDevice runs integration tests for pool device.
// The following scenario implemented:
// - Create pool device with name 'test-pool-device'
// - Create two thin volumes 'thin-1' and 'thin-2'
// - Write ext4 file system on 'thin-1' and make sure it'errs moutable
// - Write v1 test file on 'thin-1' volume
// - Take 'thin-1' snapshot 'snap-1'
// - Change v1 file to v2 on 'thin-1'
// - Mount 'snap-1' and make sure test file is v1
// - Unmount volumes and remove all devices
func TestPoolDevice(t *testing.T) {
	testutil.RequiresRoot(t)

	logrus.SetLevel(logrus.DebugLevel)
	ctx := context.Background()

	tempDir := t.TempDir()

	_, loopDataDevice := createLoopbackDevice(t, tempDir)
	_, loopMetaDevice := createLoopbackDevice(t, tempDir)

	poolName := fmt.Sprintf("test-pool-device-%d", time.Now().Nanosecond())
	err := dmsetup.CreatePool(poolName, loopDataDevice, loopMetaDevice, 64*1024/dmsetup.SectorSize)
	assert.Nil(t, err, "failed to create pool %q", poolName)

	defer func() {
		// Detach loop devices and remove images
		err := mount.DetachLoopDevice(loopDataDevice, loopMetaDevice)
		assert.NoError(t, err)
	}()

	config := &Config{
		PoolName:           poolName,
		RootPath:           tempDir,
		BaseImageSize:      "16mb",
		BaseImageSizeBytes: 16 * 1024 * 1024,
		DiscardBlocks:      true,
	}

	pool, err := NewPoolDevice(ctx, config)
	assert.Nil(t, err, "can't create device pool")
	assert.True(t, pool != nil)

	defer func() {
		err := pool.RemovePool(ctx)
		assert.Nil(t, err, "can't close device pool")
	}()

	// Create thin devices
	t.Run("CreateThinDevice", func(t *testing.T) {
		testCreateThinDevice(t, pool)
	})

	// Make ext4 filesystem on 'thin-1'
	t.Run("MakeFileSystem", func(t *testing.T) {
		testMakeFileSystem(t, pool)
	})

	// Mount 'thin-1' and write v1 test file on 'thin-1' device
	err = mount.WithTempMount(ctx, getMounts(thinDevice1), func(thin1MountPath string) error {
		// Write v1 test file on 'thin-1' device
		thin1TestFilePath := filepath.Join(thin1MountPath, "TEST")
		err := os.WriteFile(thin1TestFilePath, []byte("test file (v1)"), 0700)
		assert.Nil(t, err, "failed to write test file v1 on '%s' volume", thinDevice1)

		return nil
	})

	// Take snapshot of 'thin-1'
	t.Run("CreateSnapshotDevice", func(t *testing.T) {
		testCreateSnapshot(t, pool)
	})

	// Update TEST file on 'thin-1' to v2
	err = mount.WithTempMount(ctx, getMounts(thinDevice1), func(thin1MountPath string) error {
		thin1TestFilePath := filepath.Join(thin1MountPath, "TEST")
		err = os.WriteFile(thin1TestFilePath, []byte("test file (v2)"), 0700)
		assert.Nil(t, err, "failed to write test file v2 on 'thin-1' volume after taking snapshot")

		return nil
	})

	assert.NoError(t, err)

	// Mount 'snap-1' and make sure TEST file is v1
	err = mount.WithTempMount(ctx, getMounts(snapDevice1), func(snap1MountPath string) error {
		// Read test file from snapshot device and make sure it's v1
		fileData, err := os.ReadFile(filepath.Join(snap1MountPath, "TEST"))
		assert.Nil(t, err, "couldn't read test file from '%s' device", snapDevice1)
		assert.Equal(t, "test file (v1)", string(fileData), "test file content is invalid on snapshot")

		return nil
	})

	assert.NoError(t, err)

	t.Run("DeactivateDevice", func(t *testing.T) {
		testDeactivateThinDevice(t, pool)
	})

	t.Run("RemoveDevice", func(t *testing.T) {
		testRemoveThinDevice(t, pool)
	})

	t.Run("rollbackActivate", func(t *testing.T) {
		testCreateThinDevice(t, pool)

		ctx := context.Background()

		snapDevice := "snap2"

		err := pool.CreateSnapshotDevice(ctx, thinDevice1, snapDevice, device1Size)
		assert.NoError(t, err)

		info, err := pool.metadata.GetDevice(ctx, snapDevice)
		assert.NoError(t, err)

		// Simulate a case that the device cannot be activated.
		err = pool.DeactivateDevice(ctx, info.Name, false, false)
		assert.NoError(t, err)

		err = pool.rollbackActivate(ctx, info, err)
		assert.NoError(t, err)
	})
}

func TestPoolDeviceMarkFaulty(t *testing.T) {
	store := createStore(t)
	defer cleanupStore(t, store)

	err := store.AddDevice(testCtx, &DeviceInfo{Name: "1", State: Unknown})
	assert.NoError(t, err)

	// Note: do not use 'Activated' here because pool.ensureDeviceStates() will
	// try to activate the real dm device, which will fail on a faked device.
	err = store.AddDevice(testCtx, &DeviceInfo{Name: "2", State: Deactivated})
	assert.NoError(t, err)

	pool := &PoolDevice{metadata: store}
	err = pool.ensureDeviceStates(testCtx)
	assert.NoError(t, err)

	called := 0
	err = pool.metadata.WalkDevices(testCtx, func(info *DeviceInfo) error {
		called++

		switch called {
		case 1:
			assert.Equal(t, Faulty, info.State)
			assert.Equal(t, "1", info.Name)
		case 2:
			assert.Equal(t, Deactivated, info.State)
			assert.Equal(t, "2", info.Name)
		default:
			t.Error("unexpected walk call")
		}

		return nil
	})
	assert.NoError(t, err)
	assert.Equal(t, 2, called)
}

func testCreateThinDevice(t *testing.T, pool *PoolDevice) {
	ctx := context.Background()

	err := pool.CreateThinDevice(ctx, thinDevice1, device1Size)
	assert.Nil(t, err, "can't create first thin device")

	err = pool.CreateThinDevice(ctx, thinDevice1, device1Size)
	assert.True(t, err != nil, "device pool allows duplicated device names")

	err = pool.CreateThinDevice(ctx, thinDevice2, device2Size)
	assert.Nil(t, err, "can't create second thin device")

	deviceInfo1, err := pool.metadata.GetDevice(ctx, thinDevice1)
	assert.NoError(t, err)

	deviceInfo2, err := pool.metadata.GetDevice(ctx, thinDevice2)
	assert.NoError(t, err)

	assert.True(t, deviceInfo1.DeviceID != deviceInfo2.DeviceID, "assigned device ids should be different")

	usage, err := pool.GetUsage(thinDevice1)
	assert.NoError(t, err)
	assert.Equal(t, usage, int64(0))
}

func testMakeFileSystem(t *testing.T, pool *PoolDevice) {
	devicePath := dmsetup.GetFullDevicePath(thinDevice1)
	args := []string{
		devicePath,
		"-E",
		"nodiscard,lazy_itable_init=0,lazy_journal_init=0",
	}

	output, err := exec.Command("mkfs.ext4", args...).CombinedOutput()
	assert.Nil(t, err, "failed to make filesystem on '%s': %s", thinDevice1, string(output))

	usage, err := pool.GetUsage(thinDevice1)
	assert.NoError(t, err)
	assert.True(t, usage > 0)
}

func testCreateSnapshot(t *testing.T, pool *PoolDevice) {
	err := pool.CreateSnapshotDevice(context.Background(), thinDevice1, snapDevice1, device1Size)
	assert.Nil(t, err, "failed to create snapshot from '%s' volume", thinDevice1)
}

func testDeactivateThinDevice(t *testing.T, pool *PoolDevice) {
	deviceList := []string{
		thinDevice2,
		snapDevice1,
	}

	for _, deviceName := range deviceList {
		assert.True(t, pool.IsActivated(deviceName))

		err := pool.DeactivateDevice(context.Background(), deviceName, false, true)
		assert.Nil(t, err, "failed to remove '%s'", deviceName)

		assert.False(t, pool.IsActivated(deviceName))
	}
}

func testRemoveThinDevice(t *testing.T, pool *PoolDevice) {
	err := pool.RemoveDevice(testCtx, thinDevice1)
	assert.Nil(t, err, "should delete thin device from pool")

	err = pool.RemoveDevice(testCtx, thinDevice2)
	assert.Nil(t, err, "should delete thin device from pool")
}

func getMounts(thinDeviceName string) []mount.Mount {
	return []mount.Mount{
		{
			Source: dmsetup.GetFullDevicePath(thinDeviceName),
			Type:   "ext4",
		},
	}
}

func createLoopbackDevice(t *testing.T, dir string) (string, string) {
	file, err := os.CreateTemp(dir, testsPrefix)
	assert.NoError(t, err)

	size, err := units.RAMInBytes("128Mb")
	assert.NoError(t, err)

	err = file.Truncate(size)
	assert.NoError(t, err)

	err = file.Close()
	assert.NoError(t, err)

	imagePath := file.Name()

	loopDevice, err := mount.AttachLoopDevice(imagePath)
	assert.NoError(t, err)

	return imagePath, loopDevice
}
<|endoftext|>