<commit_before>// Copyright 2017-2019 Authors of Cilium
//
// 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 launch

import (
	"context"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/datapath/linux/route"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/endpoint"
	"github.com/cilium/cilium/pkg/endpoint/connector"
	"github.com/cilium/cilium/pkg/endpoint/regeneration"
	healthDefaults "github.com/cilium/cilium/pkg/health/defaults"
	"github.com/cilium/cilium/pkg/health/probe"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/launcher"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/metrics"
	"github.com/cilium/cilium/pkg/mtu"
	"github.com/cilium/cilium/pkg/netns"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/pidfile"

	"github.com/containernetworking/plugins/pkg/ns"
	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"
)

const (
	ciliumHealth = "cilium-health"
	netNSName    = "cilium-health"
	binaryName   = "cilium-health-responder"
)

var (
	// vethName is the host-side veth link device name for cilium-health EP
	// (veth mode only).
	vethName = "lxc_health"

	// legacyVethName is the host-side cilium-health EP device name used in
	// older Cilium versions. Used for removal only.
	legacyVethName = "cilium_health"

	// epIfaceName is the endpoint-side link device name for cilium-health.
	epIfaceName = "cilium"

	// PidfilePath
	PidfilePath = "health-endpoint.pid"

	// LaunchTime is the expected time within which the health endpoint
	// should be able to be successfully run and its BPF program attached.
	LaunchTime = 30 * time.Second
)

func configureHealthRouting(netns, dev string, addressing *models.NodeAddressing, mtuConfig mtu.Configuration) error {
	routes := []route.Route{}

	if option.Config.EnableIPv4 {
		v4Routes, err := connector.IPv4Routes(addressing, mtuConfig.GetRouteMTU())
		if err == nil {
			routes = append(routes, v4Routes...)
		} else {
			log.Debugf("Couldn't get IPv4 routes for health routing")
		}
	}

	if option.Config.EnableIPv6 {
		v6Routes, err := connector.IPv6Routes(addressing, mtuConfig.GetRouteMTU())
		if err != nil {
			return fmt.Errorf("Failed to get IPv6 routes")
		}
		routes = append(routes, v6Routes...)
	}

	prog := "ip"
	args := []string{"netns", "exec", netns, "bash", "-c"}
	routeCmds := []string{}
	for _, rt := range routes {
		cmd := strings.Join(rt.ToIPCommand(dev), " ")
		log.WithField("netns", netns).WithField("command", cmd).Debug("Adding route")
		routeCmds = append(routeCmds, cmd)
	}
	cmd := strings.Join(routeCmds, " && ")
	args = append(args, cmd)

	log.Debugf("Running \"%s %+v\"", prog, args)
	out, err := exec.Command(prog, args...).CombinedOutput()
	if err == nil && len(out) > 0 {
		log.Warn(out)
	}

	return err
}

func configureHealthInterface(netNS ns.NetNS, ifName string, ip4Addr, ip6Addr *net.IPNet) error {
	return netNS.Do(func(_ ns.NetNS) error {
		link, err := netlink.LinkByName(ifName)
		if err != nil {
			return err
		}

		if ip6Addr != nil {
			if err = netlink.AddrAdd(link, &netlink.Addr{IPNet: ip6Addr}); err != nil {
				return err
			}
		}

		if ip4Addr != nil {
			if err = netlink.AddrAdd(link, &netlink.Addr{IPNet: ip4Addr}); err != nil {
				return err
			}
		}

		if err = netlink.LinkSetUp(link); err != nil {
			return err
		}

		lo, err := netlink.LinkByName("lo")
		if err != nil {
			return err
		}

		if err = netlink.LinkSetUp(lo); err != nil {
			return err
		}

		return nil
	})
}

// Client wraps a client to a specific cilium-health endpoint instance, to
// provide convenience methods such as PingEndpoint().
type Client struct {
	host string
}

// PingEndpoint attempts to make an API ping request to the local cilium-health
// endpoint, and returns whether this was successful.
func (c *Client) PingEndpoint() error {
	return probe.GetHello(c.host)
}

// KillEndpoint attempts to kill any existing cilium-health endpoint if it
// exists.
//
// This is intended to be invoked in multiple situations:
// * The health endpoint has never been run before
// * The health endpoint was run during a previous run of the Cilium agent
// * The health endpoint crashed during the current run of the Cilium agent
//   and needs to be cleaned up before it is restarted.
func KillEndpoint() {
	path := filepath.Join(option.Config.StateDir, PidfilePath)
	scopedLog := log.WithField(logfields.PIDFile, path)
	scopedLog.Debug("Killing old health endpoint process")
	pid, err := pidfile.Kill(path)
	if err != nil {
		scopedLog.WithError(err).Warning("Failed to kill cilium-health-responder")
	} else if pid != 0 {
		scopedLog.WithField(logfields.PID, pid).Debug("Killed endpoint process")
	}
}

// CleanupEndpoint cleans up remaining resources associated with the health
// endpoint.
//
// This is expected to be called after the process is killed and the endpoint
// is removed from the endpointmanager.
func CleanupEndpoint() {
	// Removes the interfaces used for the endpoint process, followed by the
	// deletion of the health namespace itself. The removal of the interfaces
	// is needed, because network namespace removal does not always trigger the
	// deletion of associated interfaces immediately (e.g. when a process in the
	// namespace marked for deletion has not yet been terminated).
	switch option.Config.DatapathMode {
	case option.DatapathModeVeth:
		for _, iface := range []string{legacyVethName, vethName} {
			scopedLog := log.WithField(logfields.Veth, iface)
			if link, err := netlink.LinkByName(iface); err == nil {
				err = netlink.LinkDel(link)
				if err != nil {
					scopedLog.WithError(err).Info("Couldn't delete cilium-health veth device")
				}
			} else {
				scopedLog.WithError(err).Debug("Didn't find existing device")
			}
		}
	case option.DatapathModeIpvlan:
		if err := netns.RemoveIfFromNetNSWithNameIfBothExist(netNSName, epIfaceName); err != nil {
			log.WithError(err).WithField(logfields.Ipvlan, epIfaceName).
				Info("Couldn't delete cilium-health ipvlan slave device")
		}
	}

	if err := netns.RemoveNetNSWithName(netNSName); err != nil {
		log.WithError(err).Debug("Unable to remove cilium-health namespace")
	}
}

// EndpointAdder is any type which adds an endpoint to be managed by Cilium.
type EndpointAdder interface {
	AddEndpoint(owner regeneration.Owner, ep *endpoint.Endpoint, reason string) error
}

// LaunchAsEndpoint launches the cilium-health agent in a nested network
// namespace and attaches it to Cilium the same way as any other endpoint,
// but with special reserved labels.
//
// CleanupEndpoint() must be called before calling LaunchAsEndpoint() to ensure
// cleanup of prior cilium-health endpoint instances.
func LaunchAsEndpoint(baseCtx context.Context, owner regeneration.Owner, n *node.Node, mtuConfig mtu.Configuration, epMgr EndpointAdder) (*Client, error) {
	var (
		cmd  = launcher.Launcher{}
		info = &models.EndpointChangeRequest{
			ContainerName: ciliumHealth,
			State:         models.EndpointStateWaitingForIdentity,
			Addressing:    &models.AddressPair{},
		}
		healthIP               net.IP
		ip4Address, ip6Address *net.IPNet
	)

	if n.IPv6HealthIP != nil {
		healthIP = n.IPv6HealthIP
		info.Addressing.IPV6 = healthIP.String()
		ip6Address = &net.IPNet{IP: healthIP, Mask: defaults.ContainerIPv6Mask}
	}
	if n.IPv4HealthIP != nil {
		healthIP = n.IPv4HealthIP
		info.Addressing.IPV4 = healthIP.String()
		ip4Address = &net.IPNet{IP: healthIP, Mask: defaults.ContainerIPv4Mask}
	}

	if option.Config.EnableEndpointRoutes {
		dpConfig := &models.EndpointDatapathConfiguration{
			InstallEndpointRoute: true,
			RequireEgressProg:    true,
		}
		info.DatapathConfiguration = dpConfig
	}

	netNS, err := netns.ReplaceNetNSWithName(netNSName)
	if err != nil {
		return nil, err
	}

	switch option.Config.DatapathMode {
	case option.DatapathModeVeth:
		_, epLink, err := connector.SetupVethWithNames(vethName, epIfaceName, mtuConfig.GetDeviceMTU(), info)
		if err != nil {
			return nil, fmt.Errorf("Error while creating veth: %s", err)
		}

		if err = netlink.LinkSetNsFd(*epLink, int(netNS.Fd())); err != nil {
			return nil, fmt.Errorf("failed to move device %q to health namespace: %s", epIfaceName, err)
		}

	case option.DatapathModeIpvlan:
		mapFD, err := connector.CreateAndSetupIpvlanSlave("",
			epIfaceName, netNS, mtuConfig.GetDeviceMTU(),
			option.Config.Ipvlan.MasterDeviceIndex,
			option.Config.Ipvlan.OperationMode, info)
		if err != nil {
			if errDel := netns.RemoveNetNSWithName(netNSName); errDel != nil {
				log.WithError(errDel).WithField(logfields.NetNSName, netNSName).
					Warning("Unable to remove network namespace")
			}
			return nil, err
		}
		defer unix.Close(mapFD)

	}

	if err = configureHealthInterface(netNS, epIfaceName, ip4Address, ip6Address); err != nil {
		return nil, fmt.Errorf("failed configure health interface %q: %s", epIfaceName, err)
	}

	pidfile := filepath.Join(option.Config.StateDir, PidfilePath)
	prog := "ip"
	args := []string{"netns", "exec", netNSName, binaryName, "--pidfile", pidfile}
	cmd.SetTarget(prog)
	cmd.SetArgs(args)
	log.Infof("Spawning health endpoint with command %q %q", prog, args)
	if err := cmd.Run(); err != nil {
		return nil, err
	}

	// Create the endpoint
	ep, err := endpoint.NewEndpointFromChangeModel(owner, info)
	if err != nil {
		return nil, fmt.Errorf("Error while creating endpoint model: %s", err)
	}

	// Wait until the cilium-health endpoint is running before setting up routes
	deadline := time.Now().Add(1 * time.Minute)
	for {
		if _, err := os.Stat(pidfile); err == nil {
			log.WithField("pidfile", pidfile).Debug("cilium-health agent running")
			break
		} else if time.Now().After(deadline) {
			return nil, fmt.Errorf("Endpoint failed to run: %s", err)
		} else {
			time.Sleep(1 * time.Second)
		}
	}

	// Set up the endpoint routes
	hostAddressing := node.GetNodeAddressing()
	if err = configureHealthRouting(info.ContainerName, epIfaceName, hostAddressing, mtuConfig); err != nil {
		return nil, fmt.Errorf("Error while configuring routes: %s", err)
	}

	if err := epMgr.AddEndpoint(owner, ep, "Create cilium-health endpoint"); err != nil {
		return nil, fmt.Errorf("Error while adding endpoint: %s", err)
	}

	if err := ep.PinDatapathMap(); err != nil {
		return nil, err
	}

	// Give the endpoint a security identity
	ctx, cancel := context.WithTimeout(baseCtx, LaunchTime)
	defer cancel()
	ep.UpdateLabels(ctx, labels.LabelHealth, nil, true)

	// Initialize the health client to talk to this instance. This is why
	// the caller must limit usage of this package to a single goroutine.
	client := &Client{host: "http://" + net.JoinHostPort(healthIP.String(), fmt.Sprintf("%d", healthDefaults.HTTPPathPort))}
	if err = client.PingEndpoint(); err != nil {
		return nil, fmt.Errorf("Cannot establish connection to health endpoint: %s", err)
	}
	metrics.SubprocessStart.WithLabelValues(ciliumHealth).Inc()

	return client, nil
}
<commit_msg>Do not ping during preflight checks<commit_after>// Copyright 2017-2019 Authors of Cilium
//
// 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 launch

import (
	"context"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/datapath/linux/route"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/endpoint"
	"github.com/cilium/cilium/pkg/endpoint/connector"
	"github.com/cilium/cilium/pkg/endpoint/regeneration"
	healthDefaults "github.com/cilium/cilium/pkg/health/defaults"
	"github.com/cilium/cilium/pkg/health/probe"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/launcher"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/metrics"
	"github.com/cilium/cilium/pkg/mtu"
	"github.com/cilium/cilium/pkg/netns"
	"github.com/cilium/cilium/pkg/node"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/pidfile"

	"github.com/containernetworking/plugins/pkg/ns"
	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"
)

const (
	ciliumHealth = "cilium-health"
	netNSName    = "cilium-health"
	binaryName   = "cilium-health-responder"
)

var (
	// vethName is the host-side veth link device name for cilium-health EP
	// (veth mode only).
	vethName = "lxc_health"

	// legacyVethName is the host-side cilium-health EP device name used in
	// older Cilium versions. Used for removal only.
	legacyVethName = "cilium_health"

	// epIfaceName is the endpoint-side link device name for cilium-health.
	epIfaceName = "cilium"

	// PidfilePath
	PidfilePath = "health-endpoint.pid"

	// LaunchTime is the expected time within which the health endpoint
	// should be able to be successfully run and its BPF program attached.
	LaunchTime = 30 * time.Second
)

func configureHealthRouting(netns, dev string, addressing *models.NodeAddressing, mtuConfig mtu.Configuration) error {
	routes := []route.Route{}

	if option.Config.EnableIPv4 {
		v4Routes, err := connector.IPv4Routes(addressing, mtuConfig.GetRouteMTU())
		if err == nil {
			routes = append(routes, v4Routes...)
		} else {
			log.Debugf("Couldn't get IPv4 routes for health routing")
		}
	}

	if option.Config.EnableIPv6 {
		v6Routes, err := connector.IPv6Routes(addressing, mtuConfig.GetRouteMTU())
		if err != nil {
			return fmt.Errorf("Failed to get IPv6 routes")
		}
		routes = append(routes, v6Routes...)
	}

	prog := "ip"
	args := []string{"netns", "exec", netns, "bash", "-c"}
	routeCmds := []string{}
	for _, rt := range routes {
		cmd := strings.Join(rt.ToIPCommand(dev), " ")
		log.WithField("netns", netns).WithField("command", cmd).Debug("Adding route")
		routeCmds = append(routeCmds, cmd)
	}
	cmd := strings.Join(routeCmds, " && ")
	args = append(args, cmd)

	log.Debugf("Running \"%s %+v\"", prog, args)
	out, err := exec.Command(prog, args...).CombinedOutput()
	if err == nil && len(out) > 0 {
		log.Warn(out)
	}

	return err
}

func configureHealthInterface(netNS ns.NetNS, ifName string, ip4Addr, ip6Addr *net.IPNet) error {
	return netNS.Do(func(_ ns.NetNS) error {
		link, err := netlink.LinkByName(ifName)
		if err != nil {
			return err
		}

		if ip6Addr != nil {
			if err = netlink.AddrAdd(link, &netlink.Addr{IPNet: ip6Addr}); err != nil {
				return err
			}
		}

		if ip4Addr != nil {
			if err = netlink.AddrAdd(link, &netlink.Addr{IPNet: ip4Addr}); err != nil {
				return err
			}
		}

		if err = netlink.LinkSetUp(link); err != nil {
			return err
		}

		lo, err := netlink.LinkByName("lo")
		if err != nil {
			return err
		}

		if err = netlink.LinkSetUp(lo); err != nil {
			return err
		}

		return nil
	})
}

// Client wraps a client to a specific cilium-health endpoint instance, to
// provide convenience methods such as PingEndpoint().
type Client struct {
	host string
}

// PingEndpoint attempts to make an API ping request to the local cilium-health
// endpoint, and returns whether this was successful.
func (c *Client) PingEndpoint() error {
	return probe.GetHello(c.host)
}

// KillEndpoint attempts to kill any existing cilium-health endpoint if it
// exists.
//
// This is intended to be invoked in multiple situations:
// * The health endpoint has never been run before
// * The health endpoint was run during a previous run of the Cilium agent
// * The health endpoint crashed during the current run of the Cilium agent
//   and needs to be cleaned up before it is restarted.
func KillEndpoint() {
	path := filepath.Join(option.Config.StateDir, PidfilePath)
	scopedLog := log.WithField(logfields.PIDFile, path)
	scopedLog.Debug("Killing old health endpoint process")
	pid, err := pidfile.Kill(path)
	if err != nil {
		scopedLog.WithError(err).Warning("Failed to kill cilium-health-responder")
	} else if pid != 0 {
		scopedLog.WithField(logfields.PID, pid).Debug("Killed endpoint process")
	}
}

// CleanupEndpoint cleans up remaining resources associated with the health
// endpoint.
//
// This is expected to be called after the process is killed and the endpoint
// is removed from the endpointmanager.
func CleanupEndpoint() {
	// Removes the interfaces used for the endpoint process, followed by the
	// deletion of the health namespace itself. The removal of the interfaces
	// is needed, because network namespace removal does not always trigger the
	// deletion of associated interfaces immediately (e.g. when a process in the
	// namespace marked for deletion has not yet been terminated).
	switch option.Config.DatapathMode {
	case option.DatapathModeVeth:
		for _, iface := range []string{legacyVethName, vethName} {
			scopedLog := log.WithField(logfields.Veth, iface)
			if link, err := netlink.LinkByName(iface); err == nil {
				err = netlink.LinkDel(link)
				if err != nil {
					scopedLog.WithError(err).Info("Couldn't delete cilium-health veth device")
				}
			} else {
				scopedLog.WithError(err).Debug("Didn't find existing device")
			}
		}
	case option.DatapathModeIpvlan:
		if err := netns.RemoveIfFromNetNSWithNameIfBothExist(netNSName, epIfaceName); err != nil {
			log.WithError(err).WithField(logfields.Ipvlan, epIfaceName).
				Info("Couldn't delete cilium-health ipvlan slave device")
		}
	}

	if err := netns.RemoveNetNSWithName(netNSName); err != nil {
		log.WithError(err).Debug("Unable to remove cilium-health namespace")
	}
}

// EndpointAdder is any type which adds an endpoint to be managed by Cilium.
type EndpointAdder interface {
	AddEndpoint(owner regeneration.Owner, ep *endpoint.Endpoint, reason string) error
}

// LaunchAsEndpoint launches the cilium-health agent in a nested network
// namespace and attaches it to Cilium the same way as any other endpoint,
// but with special reserved labels.
//
// CleanupEndpoint() must be called before calling LaunchAsEndpoint() to ensure
// cleanup of prior cilium-health endpoint instances.
func LaunchAsEndpoint(baseCtx context.Context, owner regeneration.Owner, n *node.Node, mtuConfig mtu.Configuration, epMgr EndpointAdder) (*Client, error) {
	var (
		cmd  = launcher.Launcher{}
		info = &models.EndpointChangeRequest{
			ContainerName: ciliumHealth,
			State:         models.EndpointStateWaitingForIdentity,
			Addressing:    &models.AddressPair{},
		}
		healthIP               net.IP
		ip4Address, ip6Address *net.IPNet
	)

	if n.IPv6HealthIP != nil {
		healthIP = n.IPv6HealthIP
		info.Addressing.IPV6 = healthIP.String()
		ip6Address = &net.IPNet{IP: healthIP, Mask: defaults.ContainerIPv6Mask}
	}
	if n.IPv4HealthIP != nil {
		healthIP = n.IPv4HealthIP
		info.Addressing.IPV4 = healthIP.String()
		ip4Address = &net.IPNet{IP: healthIP, Mask: defaults.ContainerIPv4Mask}
	}

	if option.Config.EnableEndpointRoutes {
		dpConfig := &models.EndpointDatapathConfiguration{
			InstallEndpointRoute: true,
			RequireEgressProg:    true,
		}
		info.DatapathConfiguration = dpConfig
	}

	netNS, err := netns.ReplaceNetNSWithName(netNSName)
	if err != nil {
		return nil, err
	}

	switch option.Config.DatapathMode {
	case option.DatapathModeVeth:
		_, epLink, err := connector.SetupVethWithNames(vethName, epIfaceName, mtuConfig.GetDeviceMTU(), info)
		if err != nil {
			return nil, fmt.Errorf("Error while creating veth: %s", err)
		}

		if err = netlink.LinkSetNsFd(*epLink, int(netNS.Fd())); err != nil {
			return nil, fmt.Errorf("failed to move device %q to health namespace: %s", epIfaceName, err)
		}

	case option.DatapathModeIpvlan:
		mapFD, err := connector.CreateAndSetupIpvlanSlave("",
			epIfaceName, netNS, mtuConfig.GetDeviceMTU(),
			option.Config.Ipvlan.MasterDeviceIndex,
			option.Config.Ipvlan.OperationMode, info)
		if err != nil {
			if errDel := netns.RemoveNetNSWithName(netNSName); errDel != nil {
				log.WithError(errDel).WithField(logfields.NetNSName, netNSName).
					Warning("Unable to remove network namespace")
			}
			return nil, err
		}
		defer unix.Close(mapFD)

	}

	if err = configureHealthInterface(netNS, epIfaceName, ip4Address, ip6Address); err != nil {
		return nil, fmt.Errorf("failed configure health interface %q: %s", epIfaceName, err)
	}

	pidfile := filepath.Join(option.Config.StateDir, PidfilePath)
	prog := "ip"
	args := []string{"netns", "exec", netNSName, binaryName, "--pidfile", pidfile}
	cmd.SetTarget(prog)
	cmd.SetArgs(args)
	log.Infof("Spawning health endpoint with command %q %q", prog, args)
	if err := cmd.Run(); err != nil {
		return nil, err
	}

	// Create the endpoint
	ep, err := endpoint.NewEndpointFromChangeModel(owner, info)
	if err != nil {
		return nil, fmt.Errorf("Error while creating endpoint model: %s", err)
	}

	// Wait until the cilium-health endpoint is running before setting up routes
	deadline := time.Now().Add(1 * time.Minute)
	for {
		if _, err := os.Stat(pidfile); err == nil {
			log.WithField("pidfile", pidfile).Debug("cilium-health agent running")
			break
		} else if time.Now().After(deadline) {
			return nil, fmt.Errorf("Endpoint failed to run: %s", err)
		} else {
			time.Sleep(1 * time.Second)
		}
	}

	// Set up the endpoint routes
	hostAddressing := node.GetNodeAddressing()
	if err = configureHealthRouting(info.ContainerName, epIfaceName, hostAddressing, mtuConfig); err != nil {
		return nil, fmt.Errorf("Error while configuring routes: %s", err)
	}

	if err := epMgr.AddEndpoint(owner, ep, "Create cilium-health endpoint"); err != nil {
		return nil, fmt.Errorf("Error while adding endpoint: %s", err)
	}

	if err := ep.PinDatapathMap(); err != nil {
		return nil, err
	}

	// Give the endpoint a security identity
	ctx, cancel := context.WithTimeout(baseCtx, LaunchTime)
	defer cancel()
	ep.UpdateLabels(ctx, labels.LabelHealth, nil, true)

	// Initialize the health client to talk to this instance.
	client := &Client{host: "http://" + net.JoinHostPort(healthIP.String(), fmt.Sprintf("%d", healthDefaults.HTTPPathPort))}
	metrics.SubprocessStart.WithLabelValues(ciliumHealth).Inc()

	return client, nil
}
<|endoftext|>


<commit_before>// +build ignore
package main

// START OMIT

type Dog struct {
	name string
}

type Options struct { // HL
	Name string // HL
}

func NewDog(opts *Options) *Dog { // HL
	if opts == nil { // HL
		opts := &Options{} // HL
	} // HL
	d := &Dog{name: opts.Name} // HL
	return d                   // Some initialization might be done before returning `d`. // HL
} // HL

func main() {
	d := NewDog(&Options{Name: "Taro"}) // HL
}

// END OMIT
<commit_msg>Update 2015/0220-good-package/options_ok.go<commit_after>// +build ignore
package main

// START OMIT

type Dog struct {
	name string
}

type Options struct { // HL
	Name string // HL
}

func NewDog(opts *Options) *Dog { // HL
	if opts == nil { // HL
		opts := &Options{} // HL
	} // HL
	d := &Dog{name: opts.Name} // HL
	return d // Some initialization might be done before returning `d`. // HL
} // HL

func main() {
	d := NewDog(&Options{Name: "Taro"}) // HL
}

// END OMIT
<|endoftext|>


<commit_before>package stripe

import (
	"testing"

	. "github.com/smartystreets/goconvey/convey"

	stripe "github.com/stripe/stripe-go"
	stripeSub "github.com/stripe/stripe-go/sub"
)

func TestSubscribe(t *testing.T) {
	Convey("Given nonexistent plan", t, func() {
		token, accId, email := generateFakeUserInfo()
		err := Subscribe(token, accId, email, "random_plans", "random_interval")

		So(err, ShouldEqual, ErrPlanNotFound)
	})

	Convey("Given nonexistent customer, plan", t, func() {
		token, accId, email := generateFakeUserInfo()
		err := Subscribe(token, accId, email, StartingPlan, StartingInterval)

		So(err, ShouldBeNil)

		customerModel, err := FindCustomerByOldId(accId)
		id := customerModel.ProviderCustomerId

		So(err, ShouldBeNil)
		So(customerModel, ShouldNotBeNil)

		Convey("Then it should save customer", func() {
			So(checkCustomerIsSaved(accId), ShouldBeTrue)
		})

		Convey("Then it should create an customer in Stripe", func() {
			So(checkCustomerExistsInStripe(id), ShouldBeTrue)
		})

		Convey("Then it should subscribe user to plan", func() {
			customer, err := GetCustomerFromStripe(id)
			So(err, ShouldBeNil)

			So(customer.Subs.Count, ShouldEqual, 1)
		})

		Convey("Then customer can't subscribe to same plan again", func() {
			err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
			So(err, ShouldEqual, ErrCustomerAlreadySubscribedToPlan)
		})
	})

	Convey("Given existent customer, plan", t, func() {
		token, accId, email := generateFakeUserInfo()

		_, err := CreateCustomer(token, accId, email)
		So(err, ShouldBeNil)

		err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
		So(err, ShouldBeNil)

		customerModel, err := FindCustomerByOldId(accId)
		id := customerModel.ProviderCustomerId

		Convey("Then it should subscribe user to plan", func() {
			customer, err := GetCustomerFromStripe(id)
			So(err, ShouldBeNil)

			So(customer.Subs.Count, ShouldEqual, 1)
		})
	})

	Convey("Given customer already subscribed to a plan", t, func() {
		token, accId, email := generateFakeUserInfo()

		_, err := CreateCustomer(token, accId, email)
		So(err, ShouldBeNil)

		err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
		So(err, ShouldBeNil)

		customer, err := FindCustomerByOldId(accId)
		So(err, ShouldBeNil)

		customerId := customer.ProviderCustomerId

		subs, err := FindCustomerActiveSubscriptions(customer)
		So(err, ShouldBeNil)

		So(len(subs), ShouldEqual, 1)

		currentSub := subs[0]
		subId := currentSub.ProviderSubscriptionId

		Convey("Then customer can't subscribe to same plan again", func() {
			err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
			So(err, ShouldEqual, ErrCustomerAlreadySubscribedToPlan)
		})

		Convey("When customer upgrades to higher plan", func() {
			err = Subscribe(token, accId, email, HigherPlan, HigherInterval)
			So(err, ShouldBeNil)

			Convey("Then subscription is updated on stripe", func() {
				subParams := &stripe.SubParams{Customer: customerId}
				sub, err := stripeSub.Get(subId, subParams)

				So(err, ShouldBeNil)

				So(sub.Plan.Id, ShouldEqual, HigherPlan)
			})

			Convey("Then subscription is saved", func() {
				subs, err := FindCustomerActiveSubscriptions(customer)
				So(err, ShouldBeNil)

				So(len(subs), ShouldEqual, 1)

				currentSub := subs[0]
				newPlan, err := FindPlanByTitleAndInterval(HigherPlan, HigherInterval)

				So(err, ShouldBeNil)
				So(currentSub.PlanId, ShouldEqual, newPlan.Id)
			})
		})
	})

	Convey("Given customer already subscribed to a plan", t, func() {
		token, accId, email := generateFakeUserInfo()

		_, err := CreateCustomer(token, accId, email)
		So(err, ShouldBeNil)

		err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
		So(err, ShouldBeNil)

		customer, err := FindCustomerByOldId(accId)
		So(err, ShouldBeNil)

		customerId := customer.ProviderCustomerId

		subs, err := FindCustomerActiveSubscriptions(customer)
		So(err, ShouldBeNil)

		So(len(subs), ShouldEqual, 1)

		currentSub := subs[0]
		subId := currentSub.ProviderSubscriptionId

		Convey("When customer downgrades to lower plan", func() {
			err = Subscribe(token, accId, email, LowerPlan, LowerInterval)
			So(err, ShouldBeNil)

			Convey("Then subscription is updated on stripe", func() {
				subParams := &stripe.SubParams{Customer: customerId}
				sub, err := stripeSub.Get(subId, subParams)

				So(err, ShouldBeNil)

				So(sub.Plan.Id, ShouldEqual, LowerPlan)
			})

			Convey("Then subscription is saved", func() {
				subs, err := FindCustomerActiveSubscriptions(customer)
				So(err, ShouldBeNil)

				So(len(subs), ShouldEqual, 1)

				currentSub := subs[0]
				newPlan, err := FindPlanByTitleAndInterval(LowerPlan, LowerInterval)

				So(err, ShouldBeNil)
				So(currentSub.PlanId, ShouldEqual, newPlan.Id)
			})
		})
	})
}
<commit_msg>payment: add webhook related type; fix broken tests<commit_after>package stripe

import (
	"testing"

	. "github.com/smartystreets/goconvey/convey"

	stripe "github.com/stripe/stripe-go"
	stripeSub "github.com/stripe/stripe-go/sub"
)

func TestSubscribe(t *testing.T) {
	Convey("Given nonexistent plan", t, func() {
		token, accId, email := generateFakeUserInfo()
		err := Subscribe(token, accId, email, "random_plans", "random_interval")

		So(err, ShouldEqual, ErrPlanNotFound)
	})

	Convey("Given nonexistent customer, plan", t, func() {
		token, accId, email := generateFakeUserInfo()
		err := Subscribe(token, accId, email, StartingPlan, StartingInterval)

		So(err, ShouldBeNil)

		customerModel, err := FindCustomerByOldId(accId)
		id := customerModel.ProviderCustomerId

		So(err, ShouldBeNil)
		So(customerModel, ShouldNotBeNil)

		Convey("Then it should save customer", func() {
			So(checkCustomerIsSaved(accId), ShouldBeTrue)
		})

		Convey("Then it should create an customer in Stripe", func() {
			So(checkCustomerExistsInStripe(id), ShouldBeTrue)
		})

		Convey("Then it should subscribe user to plan", func() {
			customer, err := GetCustomerFromStripe(id)
			So(err, ShouldBeNil)

			So(customer.Subs.Count, ShouldEqual, 1)
		})

		Convey("Then customer can't subscribe to same plan again", func() {
			err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
			So(err, ShouldEqual, ErrCustomerAlreadySubscribedToPlan)
		})
	})

	Convey("Given existent customer, plan", t, func() {
		token, accId, email := generateFakeUserInfo()

		_, err := CreateCustomer(token, accId, email)
		So(err, ShouldBeNil)

		err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
		So(err, ShouldBeNil)

		customerModel, err := FindCustomerByOldId(accId)
		id := customerModel.ProviderCustomerId

		Convey("Then it should subscribe user to plan", func() {
			customer, err := GetCustomerFromStripe(id)
			So(err, ShouldBeNil)

			So(customer.Subs.Count, ShouldEqual, 1)
		})
	})

	Convey("Given customer already subscribed to a plan", t, func() {
		token, accId, email := generateFakeUserInfo()

		_, err := CreateCustomer(token, accId, email)
		So(err, ShouldBeNil)

		err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
		So(err, ShouldBeNil)

		customer, err := FindCustomerByOldId(accId)
		So(err, ShouldBeNil)

		customerId := customer.ProviderCustomerId

		subs, err := FindCustomerActiveSubscriptions(customer)
		So(err, ShouldBeNil)

		So(len(subs), ShouldEqual, 1)

		currentSub := subs[0]
		subId := currentSub.ProviderSubscriptionId

		Convey("Then customer can't subscribe to same plan again", func() {
			err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
			So(err, ShouldEqual, ErrCustomerAlreadySubscribedToPlan)
		})

		Convey("When customer upgrades to higher plan", func() {
			err = Subscribe(token, accId, email, HigherPlan, HigherInterval)
			So(err, ShouldBeNil)

			Convey("Then subscription is updated on stripe", func() {
				subParams := &stripe.SubParams{Customer: customerId}
				sub, err := stripeSub.Get(subId, subParams)

				So(err, ShouldBeNil)

				So(sub.Plan.Id, ShouldEqual, HigherPlan+"_"+HigherInterval)
			})

			Convey("Then subscription is saved", func() {
				subs, err := FindCustomerActiveSubscriptions(customer)
				So(err, ShouldBeNil)

				So(len(subs), ShouldEqual, 1)

				currentSub := subs[0]
				newPlan, err := FindPlanByTitleAndInterval(HigherPlan, HigherInterval)

				So(err, ShouldBeNil)
				So(currentSub.PlanId, ShouldEqual, newPlan.Id)
			})
		})
	})

	Convey("Given customer already subscribed to a plan", t, func() {
		token, accId, email := generateFakeUserInfo()

		_, err := CreateCustomer(token, accId, email)
		So(err, ShouldBeNil)

		err = Subscribe(token, accId, email, StartingPlan, StartingInterval)
		So(err, ShouldBeNil)

		customer, err := FindCustomerByOldId(accId)
		So(err, ShouldBeNil)

		customerId := customer.ProviderCustomerId

		subs, err := FindCustomerActiveSubscriptions(customer)
		So(err, ShouldBeNil)

		So(len(subs), ShouldEqual, 1)

		currentSub := subs[0]
		subId := currentSub.ProviderSubscriptionId

		Convey("When customer downgrades to lower plan", func() {
			err = Subscribe(token, accId, email, LowerPlan, LowerInterval)
			So(err, ShouldBeNil)

			Convey("Then subscription is updated on stripe", func() {
				subParams := &stripe.SubParams{Customer: customerId}
				sub, err := stripeSub.Get(subId, subParams)

				So(err, ShouldBeNil)

				So(sub.Plan.Id, ShouldEqual, LowerPlan+"_"+LowerInterval)
			})

			Convey("Then subscription is saved", func() {
				subs, err := FindCustomerActiveSubscriptions(customer)
				So(err, ShouldBeNil)

				So(len(subs), ShouldEqual, 1)

				currentSub := subs[0]
				newPlan, err := FindPlanByTitleAndInterval(LowerPlan, LowerInterval)

				So(err, ShouldBeNil)
				So(currentSub.PlanId, ShouldEqual, newPlan.Id)
			})
		})
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2022 The Vitess 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 newfeaturetest

import (
	"context"
	"os/exec"
	"testing"
	"time"

	"vitess.io/vitess/go/test/endtoend/cluster"
	"vitess.io/vitess/go/test/endtoend/reparent/utils"
	"vitess.io/vitess/go/vt/log"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// PRS TESTS

// TestPRSForInitialization tests whether calling PRS in the beginning sets up the cluster properly or not
func TestPRSForInitialization(t *testing.T) {
	var tablets []*cluster.Vttablet
	clusterInstance := cluster.NewCluster("zone1", "localhost")
	keyspace := &cluster.Keyspace{Name: utils.KeyspaceName}
	// Start topo server
	err := clusterInstance.StartTopo()
	require.NoError(t, err)
	err = clusterInstance.TopoProcess.ManageTopoDir("mkdir", "/vitess/"+"zone1")
	require.NoError(t, err)
	for i := 0; i < 4; i++ {
		tablet := clusterInstance.NewVttabletInstance("replica", 100+i, "zone1")
		tablets = append(tablets, tablet)
	}

	shard := &cluster.Shard{Name: utils.ShardName}
	shard.Vttablets = tablets
	clusterInstance.VtTabletExtraArgs = []string{
		"-lock_tables_timeout", "5s",
		"-enable_semi_sync",
		"-init_populate_metadata",
		"-track_schema_versions=true",
	}

	// Initialize Cluster
	err = clusterInstance.SetupCluster(keyspace, []cluster.Shard{*shard})
	require.NoError(t, err)

	//Start MySql
	var mysqlCtlProcessList []*exec.Cmd
	for _, shard := range clusterInstance.Keyspaces[0].Shards {
		for _, tablet := range shard.Vttablets {
			log.Infof("Starting MySql for tablet %v", tablet.Alias)
			proc, err := tablet.MysqlctlProcess.StartProcess()
			require.NoError(t, err)
			mysqlCtlProcessList = append(mysqlCtlProcessList, proc)
		}
	}
	// Wait for mysql processes to start
	for _, proc := range mysqlCtlProcessList {
		if err := proc.Wait(); err != nil {
			t.Fatalf("Error starting mysql: %s", err.Error())
		}
	}

	for _, tablet := range tablets {
		// Start the tablet
		err = tablet.VttabletProcess.Setup()
		require.NoError(t, err)
	}
	for _, tablet := range tablets {
		err := tablet.VttabletProcess.WaitForTabletStatuses([]string{"SERVING", "NOT_SERVING"})
		require.NoError(t, err)
	}

	// Force the replica to reparent assuming that all the datasets are identical.
	res, err := utils.Prs(t, clusterInstance, tablets[0])
	require.NoError(t, err, res)

	utils.ValidateTopology(t, clusterInstance, true)
	// create Tables
	utils.RunSQL(context.Background(), t, "create table vt_insert_test (id bigint, msg varchar(64), primary key (id)) Engine=InnoDB", tablets[0])
	utils.CheckPrimaryTablet(t, clusterInstance, tablets[0])
	utils.ValidateTopology(t, clusterInstance, false)
	time.Sleep(100 * time.Millisecond) // wait for replication to catchup
	strArray := utils.GetShardReplicationPositions(t, clusterInstance, utils.KeyspaceName, utils.ShardName, true)
	assert.Equal(t, len(tablets), len(strArray))
	assert.Contains(t, strArray[0], "primary") // primary first
}

// ERS TESTS

// TestERSPromoteRdonly tests that we never end up promoting a rdonly instance as the primary
func TestERSPromoteRdonly(t *testing.T) {
	defer cluster.PanicHandler(t)
	clusterInstance := utils.SetupReparentCluster(t)
	defer utils.TeardownCluster(clusterInstance)
	tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
	var err error

	err = clusterInstance.VtctlclientProcess.ExecuteCommand("ChangeTabletType", tablets[1].Alias, "rdonly")
	require.NoError(t, err)

	err = clusterInstance.VtctlclientProcess.ExecuteCommand("ChangeTabletType", tablets[2].Alias, "rdonly")
	require.NoError(t, err)

	utils.ConfirmReplication(t, tablets[0], tablets[1:])

	// Make the current primary agent and database unavailable.
	utils.StopTablet(t, tablets[0], true)

	// We expect this one to fail because we have ignored all the replicas and have only the rdonly's which should not be promoted
	out, err := utils.ErsIgnoreTablet(clusterInstance, nil, "30s", "30s", []*cluster.Vttablet{tablets[3]}, false)
	require.NotNil(t, err, out)

	out, err = clusterInstance.VtctlclientProcess.ExecuteCommandWithOutput("GetShard", utils.KeyspaceShard)
	require.NoError(t, err)
	require.Contains(t, out, `"uid": 101`, "the primary should still be 101 in the shard info")
}

// TestERSPreventCrossCellPromotion tests that we promote a replica in the same cell as the previous primary if prevent cross cell promotion flag is set
func TestERSPreventCrossCellPromotion(t *testing.T) {
	defer cluster.PanicHandler(t)
	clusterInstance := utils.SetupReparentCluster(t)
	defer utils.TeardownCluster(clusterInstance)
	tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
	var err error

	// confirm that replication is going smoothly
	utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{tablets[1], tablets[2], tablets[3]})

	// Make the current primary agent and database unavailable.
	utils.StopTablet(t, tablets[0], true)

	// We expect that tablets[2] will be promoted since it is in the same cell as the previous primary
	out, err := utils.ErsIgnoreTablet(clusterInstance, nil, "60s", "30s", []*cluster.Vttablet{tablets[1]}, true)
	require.NoError(t, err, out)

	newPrimary := utils.GetNewPrimary(t, clusterInstance)
	require.Equal(t, newPrimary.Alias, tablets[2].Alias, "tablets[2] should be the promoted primary")
}

// TestPullFromRdonly tests that if a rdonly tablet is the most advanced, then our promoted primary should have
// caught up to it by pulling transactions from it
func TestPullFromRdonly(t *testing.T) {
	defer cluster.PanicHandler(t)
	clusterInstance := utils.SetupReparentCluster(t)
	defer utils.TeardownCluster(clusterInstance)
	tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
	var err error

	ctx := context.Background()
	// make tablets[1] a rdonly tablet.
	// rename tablet so that the test is not confusing
	rdonly := tablets[1]
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("ChangeTabletType", rdonly.Alias, "rdonly")
	require.NoError(t, err)

	// confirm that all the tablets can replicate successfully right now
	utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{rdonly, tablets[2], tablets[3]})

	// stop replication on the other two tablets
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("StopReplication", tablets[2].Alias)
	require.NoError(t, err)
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("StopReplication", tablets[3].Alias)
	require.NoError(t, err)

	// stop semi-sync on the primary so that any transaction now added does not require an ack
	utils.RunSQL(ctx, t, "SET GLOBAL rpl_semi_sync_master_enabled = false", tablets[0])

	// confirm that rdonly is able to replicate from our primary
	// This will also introduce a new transaction into the rdonly tablet which the other 2 replicas don't have
	insertVal := utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{rdonly})

	// Make the current primary agent and database unavailable.
	utils.StopTablet(t, tablets[0], true)

	// start the replication back on the two tablets
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("StartReplication", tablets[2].Alias)
	require.NoError(t, err)
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("StartReplication", tablets[3].Alias)
	require.NoError(t, err)

	// check that tablets[2] and tablets[3] still only has 1 value
	err = utils.CheckCountOfInsertedValues(ctx, t, tablets[2], 1)
	require.NoError(t, err)
	err = utils.CheckCountOfInsertedValues(ctx, t, tablets[3], 1)
	require.NoError(t, err)

	// At this point we have successfully made our rdonly tablet more advanced than tablets[2] and tablets[3] without introducing errant GTIDs
	// We have simulated a network partition in which the primary and rdonly got isolated and then the primary went down leaving the rdonly most advanced

	// We expect that tablets[2] will be promoted since it is in the same cell as the previous primary
	// since we are preventing cross cell promotions
	// Also it must be fully caught up
	out, err := utils.ErsIgnoreTablet(clusterInstance, nil, "60s", "30s", nil, true)
	require.NoError(t, err, out)

	newPrimary := utils.GetNewPrimary(t, clusterInstance)
	require.Equal(t, newPrimary.Alias, tablets[2].Alias, "tablets[2] should be the promoted primary")

	// check that the new primary has the last transaction that only the rdonly had
	err = utils.CheckInsertedValues(ctx, t, newPrimary, insertVal)
	require.NoError(t, err)
}

// TestTwoReplicasNoReplicationStatus checks that ERS is able to fix
// two replicas which do not have any replication status
func TestTwoReplicasNoReplicationStatus(t *testing.T) {
	defer cluster.PanicHandler(t)
	clusterInstance := utils.SetupReparentCluster(t)
	defer utils.TeardownCluster(clusterInstance)
	tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
	utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{tablets[1], tablets[2], tablets[3]})

	err := clusterInstance.VtctlclientProcess.ExecuteCommand("ExecuteFetchAsDba", tablets[1].Alias, `STOP SLAVE; RESET SLAVE ALL`)
	require.NoError(t, err)
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("ExecuteFetchAsDba", tablets[2].Alias, `STOP SLAVE; RESET SLAVE ALL`)
	require.NoError(t, err)

	out, err := utils.Ers(clusterInstance, tablets[3], "60s", "30s")
	require.NoError(t, err, out)
}
<commit_msg>test: Augment an e2e test to also verify that ERS succeeds when the primary-elect has replication stopped<commit_after>/*
Copyright 2022 The Vitess 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 newfeaturetest

import (
	"context"
	"os/exec"
	"testing"
	"time"

	"vitess.io/vitess/go/test/endtoend/cluster"
	"vitess.io/vitess/go/test/endtoend/reparent/utils"
	"vitess.io/vitess/go/vt/log"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// PRS TESTS

// TestPRSForInitialization tests whether calling PRS in the beginning sets up the cluster properly or not
func TestPRSForInitialization(t *testing.T) {
	var tablets []*cluster.Vttablet
	clusterInstance := cluster.NewCluster("zone1", "localhost")
	keyspace := &cluster.Keyspace{Name: utils.KeyspaceName}
	// Start topo server
	err := clusterInstance.StartTopo()
	require.NoError(t, err)
	err = clusterInstance.TopoProcess.ManageTopoDir("mkdir", "/vitess/"+"zone1")
	require.NoError(t, err)
	for i := 0; i < 4; i++ {
		tablet := clusterInstance.NewVttabletInstance("replica", 100+i, "zone1")
		tablets = append(tablets, tablet)
	}

	shard := &cluster.Shard{Name: utils.ShardName}
	shard.Vttablets = tablets
	clusterInstance.VtTabletExtraArgs = []string{
		"-lock_tables_timeout", "5s",
		"-enable_semi_sync",
		"-init_populate_metadata",
		"-track_schema_versions=true",
	}

	// Initialize Cluster
	err = clusterInstance.SetupCluster(keyspace, []cluster.Shard{*shard})
	require.NoError(t, err)

	//Start MySql
	var mysqlCtlProcessList []*exec.Cmd
	for _, shard := range clusterInstance.Keyspaces[0].Shards {
		for _, tablet := range shard.Vttablets {
			log.Infof("Starting MySql for tablet %v", tablet.Alias)
			proc, err := tablet.MysqlctlProcess.StartProcess()
			require.NoError(t, err)
			mysqlCtlProcessList = append(mysqlCtlProcessList, proc)
		}
	}
	// Wait for mysql processes to start
	for _, proc := range mysqlCtlProcessList {
		if err := proc.Wait(); err != nil {
			t.Fatalf("Error starting mysql: %s", err.Error())
		}
	}

	for _, tablet := range tablets {
		// Start the tablet
		err = tablet.VttabletProcess.Setup()
		require.NoError(t, err)
	}
	for _, tablet := range tablets {
		err := tablet.VttabletProcess.WaitForTabletStatuses([]string{"SERVING", "NOT_SERVING"})
		require.NoError(t, err)
	}

	// Force the replica to reparent assuming that all the datasets are identical.
	res, err := utils.Prs(t, clusterInstance, tablets[0])
	require.NoError(t, err, res)

	utils.ValidateTopology(t, clusterInstance, true)
	// create Tables
	utils.RunSQL(context.Background(), t, "create table vt_insert_test (id bigint, msg varchar(64), primary key (id)) Engine=InnoDB", tablets[0])
	utils.CheckPrimaryTablet(t, clusterInstance, tablets[0])
	utils.ValidateTopology(t, clusterInstance, false)
	time.Sleep(100 * time.Millisecond) // wait for replication to catchup
	strArray := utils.GetShardReplicationPositions(t, clusterInstance, utils.KeyspaceName, utils.ShardName, true)
	assert.Equal(t, len(tablets), len(strArray))
	assert.Contains(t, strArray[0], "primary") // primary first
}

// ERS TESTS

// TestERSPromoteRdonly tests that we never end up promoting a rdonly instance as the primary
func TestERSPromoteRdonly(t *testing.T) {
	defer cluster.PanicHandler(t)
	clusterInstance := utils.SetupReparentCluster(t)
	defer utils.TeardownCluster(clusterInstance)
	tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
	var err error

	err = clusterInstance.VtctlclientProcess.ExecuteCommand("ChangeTabletType", tablets[1].Alias, "rdonly")
	require.NoError(t, err)

	err = clusterInstance.VtctlclientProcess.ExecuteCommand("ChangeTabletType", tablets[2].Alias, "rdonly")
	require.NoError(t, err)

	utils.ConfirmReplication(t, tablets[0], tablets[1:])

	// Make the current primary agent and database unavailable.
	utils.StopTablet(t, tablets[0], true)

	// We expect this one to fail because we have ignored all the replicas and have only the rdonly's which should not be promoted
	out, err := utils.ErsIgnoreTablet(clusterInstance, nil, "30s", "30s", []*cluster.Vttablet{tablets[3]}, false)
	require.NotNil(t, err, out)

	out, err = clusterInstance.VtctlclientProcess.ExecuteCommandWithOutput("GetShard", utils.KeyspaceShard)
	require.NoError(t, err)
	require.Contains(t, out, `"uid": 101`, "the primary should still be 101 in the shard info")
}

// TestERSPreventCrossCellPromotion tests that we promote a replica in the same cell as the previous primary if prevent cross cell promotion flag is set
func TestERSPreventCrossCellPromotion(t *testing.T) {
	defer cluster.PanicHandler(t)
	clusterInstance := utils.SetupReparentCluster(t)
	defer utils.TeardownCluster(clusterInstance)
	tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
	var err error

	// confirm that replication is going smoothly
	utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{tablets[1], tablets[2], tablets[3]})

	// Make the current primary agent and database unavailable.
	utils.StopTablet(t, tablets[0], true)

	// We expect that tablets[2] will be promoted since it is in the same cell as the previous primary
	out, err := utils.ErsIgnoreTablet(clusterInstance, nil, "60s", "30s", []*cluster.Vttablet{tablets[1]}, true)
	require.NoError(t, err, out)

	newPrimary := utils.GetNewPrimary(t, clusterInstance)
	require.Equal(t, newPrimary.Alias, tablets[2].Alias, "tablets[2] should be the promoted primary")
}

// TestPullFromRdonly tests that if a rdonly tablet is the most advanced, then our promoted primary should have
// caught up to it by pulling transactions from it
func TestPullFromRdonly(t *testing.T) {
	defer cluster.PanicHandler(t)
	clusterInstance := utils.SetupReparentCluster(t)
	defer utils.TeardownCluster(clusterInstance)
	tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
	var err error

	ctx := context.Background()
	// make tablets[1] a rdonly tablet.
	// rename tablet so that the test is not confusing
	rdonly := tablets[1]
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("ChangeTabletType", rdonly.Alias, "rdonly")
	require.NoError(t, err)

	// confirm that all the tablets can replicate successfully right now
	utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{rdonly, tablets[2], tablets[3]})

	// stop replication on the other two tablets
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("StopReplication", tablets[2].Alias)
	require.NoError(t, err)
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("StopReplication", tablets[3].Alias)
	require.NoError(t, err)

	// stop semi-sync on the primary so that any transaction now added does not require an ack
	utils.RunSQL(ctx, t, "SET GLOBAL rpl_semi_sync_master_enabled = false", tablets[0])

	// confirm that rdonly is able to replicate from our primary
	// This will also introduce a new transaction into the rdonly tablet which the other 2 replicas don't have
	insertVal := utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{rdonly})

	// Make the current primary agent and database unavailable.
	utils.StopTablet(t, tablets[0], true)

	// start the replication back on the two tablets
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("StartReplication", tablets[2].Alias)
	require.NoError(t, err)
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("StartReplication", tablets[3].Alias)
	require.NoError(t, err)

	// check that tablets[2] and tablets[3] still only has 1 value
	err = utils.CheckCountOfInsertedValues(ctx, t, tablets[2], 1)
	require.NoError(t, err)
	err = utils.CheckCountOfInsertedValues(ctx, t, tablets[3], 1)
	require.NoError(t, err)

	// At this point we have successfully made our rdonly tablet more advanced than tablets[2] and tablets[3] without introducing errant GTIDs
	// We have simulated a network partition in which the primary and rdonly got isolated and then the primary went down leaving the rdonly most advanced

	// We expect that tablets[2] will be promoted since it is in the same cell as the previous primary
	// since we are preventing cross cell promotions
	// Also it must be fully caught up
	out, err := utils.ErsIgnoreTablet(clusterInstance, nil, "60s", "30s", nil, true)
	require.NoError(t, err, out)

	newPrimary := utils.GetNewPrimary(t, clusterInstance)
	require.Equal(t, newPrimary.Alias, tablets[2].Alias, "tablets[2] should be the promoted primary")

	// check that the new primary has the last transaction that only the rdonly had
	err = utils.CheckInsertedValues(ctx, t, newPrimary, insertVal)
	require.NoError(t, err)
}

// TestNoReplicationStatusAndReplicationStopped checks that ERS is able to fix
// replicas which do not have any replication status and also succeeds if the replication
// is stopped on the primary elect.
func TestNoReplicationStatusAndReplicationStopped(t *testing.T) {
	defer cluster.PanicHandler(t)
	clusterInstance := utils.SetupReparentCluster(t)
	defer utils.TeardownCluster(clusterInstance)
	tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
	utils.ConfirmReplication(t, tablets[0], []*cluster.Vttablet{tablets[1], tablets[2], tablets[3]})

	err := clusterInstance.VtctlclientProcess.ExecuteCommand("ExecuteFetchAsDba", tablets[1].Alias, `STOP SLAVE; RESET SLAVE ALL`)
	require.NoError(t, err)
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("ExecuteFetchAsDba", tablets[2].Alias, `STOP SLAVE;`)
	require.NoError(t, err)
	err = clusterInstance.VtctlclientProcess.ExecuteCommand("ExecuteFetchAsDba", tablets[3].Alias, `STOP SLAVE SQL_THREAD;`)
	require.NoError(t, err)
	// Run an additional command in the current primary which will only be acked by tablets[3] and be in its relay log.
	insertedVal := utils.ConfirmReplication(t, tablets[0], nil)
	// Failover to tablets[3]
	out, err := utils.Ers(clusterInstance, tablets[3], "60s", "30s")
	require.NoError(t, err, out)
	// Verify that the tablet has the inserted value
	err = utils.CheckInsertedValues(context.Background(), t, tablets[3], insertedVal)
	require.NoError(t, err)
	// Confirm that replication is setup correctly from tablets[3] to tablets[0]
	utils.ConfirmReplication(t, tablets[3], tablets[:1])
	// Confirm that tablets[2] which had replication stopped initially still has its replication stopped
	utils.CheckReplicationStatus(context.Background(), t, tablets[2], false, false)
}
<|endoftext|>


<commit_before>package carbonserver

import (
	"errors"
	_ "net/http/pprof"
	"sync/atomic"
	"time"

	"go.uber.org/zap"

	protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb"
)

func (listener *CarbonserverListener) fetchSingleMetricV3(metric string, pathExpression string, fromTime, untilTime int32) (*protov3.FetchResponse, error) {
	logger := listener.logger.With(
		zap.String("metric", metric),
		zap.Int("fromTime", int(fromTime)),
		zap.Int("untilTime", int(untilTime)),
	)
	m, err := listener.fetchFromDisk(metric, fromTime, untilTime)
	if err != nil {
		atomic.AddUint64(&listener.metrics.RenderErrors, 1)
		logger.Warn("failed to fetch points", zap.Error(err))
		return nil, err
	}

	// Should never happen, because we have a check for proper archive now
	if m.Timeseries == nil {
		atomic.AddUint64(&listener.metrics.RenderErrors, 1)
		logger.Warn("metric time range not found")
		return nil, errors.New("time range not found")
	}
	atomic.AddUint64(&listener.metrics.MetricsReturned, 1)
	values := m.Timeseries.Values()

	from := int64(m.Timeseries.FromTime())
	until := int64(m.Timeseries.UntilTime())
	step := int64(m.Timeseries.Step())

	waitTime := uint64(time.Since(m.DiskStartTime).Nanoseconds())
	atomic.AddUint64(&listener.metrics.DiskWaitTimeNS, waitTime)
	atomic.AddUint64(&listener.metrics.PointsReturned, uint64(len(values)))

	response := protov3.FetchResponse{
		Name:              metric,
		StartTime:         from,
		StopTime:          until,
		StepTime:          step,
		Values:            values,
		PathExpression:    pathExpression,
		ConsolidationFunc: m.Metadata.ConsolidationFunc,
		XFilesFactor:      m.Metadata.XFilesFactor,
	}

	if m.CacheData != nil {
		atomic.AddUint64(&listener.metrics.CacheRequestsTotal, 1)
		cacheStartTime := time.Now()
		pointsFetchedFromCache := 0
		for _, item := range m.CacheData {
			ts := int64(item.Timestamp) - int64(item.Timestamp)%step
			if ts < from || ts >= until {
				continue
			}
			pointsFetchedFromCache++
			index := (ts - from) / step
			response.Values[index] = item.Value
		}
		waitTime := uint64(time.Since(cacheStartTime).Nanoseconds())
		atomic.AddUint64(&listener.metrics.CacheWorkTimeNS, waitTime)
		if pointsFetchedFromCache > 0 {
			atomic.AddUint64(&listener.metrics.CacheHit, 1)
		} else {
			atomic.AddUint64(&listener.metrics.CacheMiss, 1)
		}
	}

	logger.Debug("fetched",
		zap.Any("response", response),
	)
	return &response, nil
}
<commit_msg>Include original from/until times in responses<commit_after>package carbonserver

import (
	"errors"
	_ "net/http/pprof"
	"sync/atomic"
	"time"

	"go.uber.org/zap"

	protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb"
)

func (listener *CarbonserverListener) fetchSingleMetricV3(metric string, pathExpression string, fromTime, untilTime int32) (*protov3.FetchResponse, error) {
	logger := listener.logger.With(
		zap.String("metric", metric),
		zap.Int("fromTime", int(fromTime)),
		zap.Int("untilTime", int(untilTime)),
	)
	m, err := listener.fetchFromDisk(metric, fromTime, untilTime)
	if err != nil {
		atomic.AddUint64(&listener.metrics.RenderErrors, 1)
		logger.Warn("failed to fetch points", zap.Error(err))
		return nil, err
	}

	// Should never happen, because we have a check for proper archive now
	if m.Timeseries == nil {
		atomic.AddUint64(&listener.metrics.RenderErrors, 1)
		logger.Warn("metric time range not found")
		return nil, errors.New("time range not found")
	}
	atomic.AddUint64(&listener.metrics.MetricsReturned, 1)
	values := m.Timeseries.Values()

	from := int64(m.Timeseries.FromTime())
	until := int64(m.Timeseries.UntilTime())
	step := int64(m.Timeseries.Step())

	waitTime := uint64(time.Since(m.DiskStartTime).Nanoseconds())
	atomic.AddUint64(&listener.metrics.DiskWaitTimeNS, waitTime)
	atomic.AddUint64(&listener.metrics.PointsReturned, uint64(len(values)))

	response := protov3.FetchResponse{
		Name:              metric,
		StartTime:         from,
		StopTime:          until,
		StepTime:          step,
		Values:            values,
		PathExpression:    pathExpression,
		ConsolidationFunc: m.Metadata.ConsolidationFunc,
		XFilesFactor:      m.Metadata.XFilesFactor,
		RequestStartTime:  int64(fromTime),
		RequestStopTime:   int64(untilTime),
	}

	if m.CacheData != nil {
		atomic.AddUint64(&listener.metrics.CacheRequestsTotal, 1)
		cacheStartTime := time.Now()
		pointsFetchedFromCache := 0
		for _, item := range m.CacheData {
			ts := int64(item.Timestamp) - int64(item.Timestamp)%step
			if ts < from || ts >= until {
				continue
			}
			pointsFetchedFromCache++
			index := (ts - from) / step
			response.Values[index] = item.Value
		}
		waitTime := uint64(time.Since(cacheStartTime).Nanoseconds())
		atomic.AddUint64(&listener.metrics.CacheWorkTimeNS, waitTime)
		if pointsFetchedFromCache > 0 {
			atomic.AddUint64(&listener.metrics.CacheHit, 1)
		} else {
			atomic.AddUint64(&listener.metrics.CacheMiss, 1)
		}
	}

	logger.Debug("fetched",
		zap.Any("response", response),
	)
	return &response, nil
}
<|endoftext|>


<commit_before>package transactionpool

import (
	"github.com/NebulousLabs/Sia/types"
)

// transactions.go is a temporary file filled with deprecated functions.
// Eventually, all modules dependent on the TransactionSet() function will be
// altered so that they are instead dependent on subscriptions. To my
// knowledge, only siad still needs to be transitioned.

// TransactionSet returns the set of unconfirmed transactions in the order
// they are required to appear in a block. This function will not limit the
// volume of transactions to fit in a single block.
func (tp *TransactionPool) TransactionSet() []types.Transaction {
	id := tp.mu.RLock()
	defer tp.mu.RUnlock(id)
	return tp.transactionList
}
<commit_msg>Undid the transactionSet() removal<commit_after>package transactionpool

import (
	"github.com/NebulousLabs/Sia/types"
)

// transactions.go is a temporary file filled with deprecated functions.
// Eventually, all modules dependent on the TransactionSet() function will be
// altered so that they are instead dependent on subscriptions. To my
// knowledge, only siad still needs to be transitioned.

// transactionSet returns the set of unconfirmed transactions in the order
// they are required to appear in a block. This function will not limit the
// volume of transactions to fit in a single block.
func (tp *TransactionPool) transactionSet() (set []types.Transaction) {
	for _, txn := range tp.transactionList {
		set = append(set, txn)
	}
	return
}

// TransactionSet returns the set of unconfirmed transactions in the order
// they are required to appear in a block. This function will not limit the
// volume of transactions to fit in a single block.
func (tp *TransactionPool) TransactionSet() []types.Transaction {
	id := tp.mu.RLock()
	defer tp.mu.RUnlock(id)
	return tp.transactionSet()
}
<|endoftext|>


<commit_before>package service_test

import (
	"os"

	"github.com/cloudfoundry/cli/cf/commandregistry"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/trace/tracefakes"
	"github.com/cloudfoundry/cli/plugin/models"
	testcmd "github.com/cloudfoundry/cli/testhelpers/commands"
	testreq "github.com/cloudfoundry/cli/testhelpers/requirements"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"

	. "github.com/cloudfoundry/cli/cf/commands/service"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("service command", func() {
	var (
		ui                  *testterm.FakeUI
		requirementsFactory *testreq.FakeReqFactory
		deps                commandregistry.Dependency
	)

	updateCommandDependency := func(pluginCall bool) {
		deps.UI = ui
		commandregistry.Commands.SetCommand(commandregistry.Commands.FindCommand("service").SetDependency(deps, pluginCall))
	}

	BeforeEach(func() {
		ui = &testterm.FakeUI{}
		requirementsFactory = &testreq.FakeReqFactory{}

		deps = commandregistry.NewDependency(os.Stdout, new(tracefakes.FakePrinter))
	})

	runCommand := func(args ...string) bool {
		return testcmd.RunCLICommand("service", args, requirementsFactory, updateCommandDependency, false)
	}

	Describe("requirements", func() {
		It("fails when not provided the name of the service to show", func() {
			requirementsFactory.LoginSuccess = true
			requirementsFactory.TargetedSpaceSuccess = true
			runCommand()

			Expect(ui.Outputs).To(ContainSubstrings(
				[]string{"Incorrect Usage", "Requires an argument"},
			))
		})

		It("fails when not logged in", func() {
			requirementsFactory.TargetedSpaceSuccess = true

			Expect(runCommand("come-ON")).To(BeFalse())
		})

		It("fails when a space is not targeted", func() {
			requirementsFactory.LoginSuccess = true

			Expect(runCommand("okay-this-time-please??")).To(BeFalse())
		})
	})

	Describe("After Requirement", func() {
		createServiceInstanceWithState := func(state string) {
			offering := models.ServiceOfferingFields{Label: "mysql", DocumentationURL: "http://documentation.url", Description: "the-description"}
			plan := models.ServicePlanFields{GUID: "plan-guid", Name: "plan-name"}

			serviceInstance := models.ServiceInstance{}
			serviceInstance.Name = "service1"
			serviceInstance.GUID = "service1-guid"
			serviceInstance.LastOperation.Type = "create"
			serviceInstance.LastOperation.State = "in progress"
			serviceInstance.LastOperation.Description = "creating resource - step 1"
			serviceInstance.ServicePlan = plan
			serviceInstance.ServiceOffering = offering
			serviceInstance.DashboardURL = "some-url"
			serviceInstance.LastOperation.State = state
			serviceInstance.LastOperation.CreatedAt = "created-date"
			serviceInstance.LastOperation.UpdatedAt = "updated-date"
			requirementsFactory.ServiceInstance = serviceInstance
		}

		createServiceInstance := func() {
			createServiceInstanceWithState("")
		}

		Describe("when invoked by a plugin", func() {
			var (
				pluginModel *plugin_models.GetService_Model
			)

			BeforeEach(func() {
				requirementsFactory.LoginSuccess = true
				requirementsFactory.TargetedSpaceSuccess = true

				pluginModel = &plugin_models.GetService_Model{}
				deps.PluginModels.Service = pluginModel
			})

			It("populates the plugin model upon execution", func() {
				createServiceInstanceWithState("in progress")
				testcmd.RunCLICommand("service", []string{"service1"}, requirementsFactory, updateCommandDependency, true)
				Expect(pluginModel.Name).To(Equal("service1"))
				Expect(pluginModel.Guid).To(Equal("service1-guid"))
				Expect(pluginModel.LastOperation.Type).To(Equal("create"))
				Expect(pluginModel.LastOperation.State).To(Equal("in progress"))
				Expect(pluginModel.LastOperation.Description).To(Equal("creating resource - step 1"))
				Expect(pluginModel.LastOperation.CreatedAt).To(Equal("created-date"))
				Expect(pluginModel.LastOperation.UpdatedAt).To(Equal("updated-date"))
				Expect(pluginModel.LastOperation.Type).To(Equal("create"))
				Expect(pluginModel.ServicePlan.Name).To(Equal("plan-name"))
				Expect(pluginModel.ServicePlan.Guid).To(Equal("plan-guid"))
				Expect(pluginModel.ServiceOffering.DocumentationUrl).To(Equal("http://documentation.url"))
				Expect(pluginModel.ServiceOffering.Name).To(Equal("mysql"))
			})
		})

		Context("when logged in, a space is targeted, and provided the name of a service that exists", func() {
			BeforeEach(func() {
				requirementsFactory.LoginSuccess = true
				requirementsFactory.TargetedSpaceSuccess = true
			})

			Context("when the service is externally provided", func() {

				It("shows the service", func() {
					createServiceInstanceWithState("in progress")
					runCommand("service1")

					Expect(ui.Outputs).To(ContainSubstrings(
						[]string{"Service instance:", "service1"},
						[]string{"Service: ", "mysql"},
						[]string{"Plan: ", "plan-name"},
						[]string{"Description: ", "the-description"},
						[]string{"Documentation url: ", "http://documentation.url"},
						[]string{"Dashboard: ", "some-url"},
						[]string{"Last Operation"},
						[]string{"Status: ", "create in progress"},
						[]string{"Message: ", "creating resource - step 1"},
						[]string{"Started: ", "created-date"},
						[]string{"Updated: ", "updated-date"},
					))
					Expect(requirementsFactory.ServiceInstanceName).To(Equal("service1"))
				})

				Context("when the service instance CreatedAt is empty", func() {
					It("does not output the Started line", func() {
						createServiceInstanceWithState("in progress")
						requirementsFactory.ServiceInstance.LastOperation.CreatedAt = ""
						runCommand("service1")

						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Service instance:", "service1"},
							[]string{"Service: ", "mysql"},
							[]string{"Plan: ", "plan-name"},
							[]string{"Description: ", "the-description"},
							[]string{"Documentation url: ", "http://documentation.url"},
							[]string{"Dashboard: ", "some-url"},
							[]string{"Last Operation"},
							[]string{"Status: ", "create in progress"},
							[]string{"Message: ", "creating resource - step 1"},
							[]string{"Updated: ", "updated-date"},
						))
						Expect(ui.Outputs).ToNot(ContainSubstrings(
							[]string{"Started: "},
						))
					})
				})

				Context("shows correct status information based on service instance state", func() {
					It("shows status: `create in progress` when state is `in progress`", func() {
						createServiceInstanceWithState("in progress")
						runCommand("service1")

						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Status: ", "create in progress"},
						))
						Expect(requirementsFactory.ServiceInstanceName).To(Equal("service1"))
					})

					It("shows status: `create succeeded` when state is `succeeded`", func() {
						createServiceInstanceWithState("succeeded")
						runCommand("service1")

						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Status: ", "create succeeded"},
						))
						Expect(requirementsFactory.ServiceInstanceName).To(Equal("service1"))
					})

					It("shows status: `create failed` when state is `failed`", func() {
						createServiceInstanceWithState("failed")
						runCommand("service1")

						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Status: ", "create failed"},
						))
						Expect(requirementsFactory.ServiceInstanceName).To(Equal("service1"))
					})

					It("shows status: `` when state is ``", func() {
						createServiceInstanceWithState("")
						runCommand("service1")

						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Status: ", ""},
						))
						Expect(requirementsFactory.ServiceInstanceName).To(Equal("service1"))
					})
				})

				Context("when the guid flag is provided", func() {
					It("shows only the service guid", func() {
						createServiceInstance()
						runCommand("--guid", "service1")

						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"service1-guid"},
						))

						Expect(ui.Outputs).ToNot(ContainSubstrings(
							[]string{"Service instance:", "service1"},
						))
					})
				})
			})

			Context("when the service is user provided", func() {
				BeforeEach(func() {
					serviceInstance := models.ServiceInstance{}
					serviceInstance.Name = "service1"
					serviceInstance.GUID = "service1-guid"
					requirementsFactory.ServiceInstance = serviceInstance
				})

				It("shows user provided services", func() {
					runCommand("service1")

					Expect(ui.Outputs).To(ContainSubstrings(
						[]string{"Service instance: ", "service1"},
						[]string{"Service: ", "user-provided"},
					))
				})
			})

			Context("when the service has tags", func() {
				BeforeEach(func() {
					serviceInstance := models.ServiceInstance{}
					serviceInstance.Tags = []string{"tag1", "tag2"}
					serviceInstance.ServicePlan = models.ServicePlanFields{GUID: "plan-guid", Name: "plan-name"}
					requirementsFactory.ServiceInstance = serviceInstance
				})

				It("includes the tags in the output", func() {
					runCommand("service1")

					Expect(ui.Outputs).To(ContainSubstrings(
						[]string{"Tags: ", "tag1, tag2"},
					))
				})
			})
		})
	})
})

var _ = Describe("ServiceInstanceStateToStatus", func() {
	var operationType string
	Context("when the service is not user provided", func() {
		isUserProvided := false

		Context("when operationType is `create`", func() {
			BeforeEach(func() { operationType = "create" })

			It("returns status: `create in progress` when state: `in progress`", func() {
				status := ServiceInstanceStateToStatus(operationType, "in progress", isUserProvided)
				Expect(status).To(Equal("create in progress"))
			})

			It("returns status: `create succeeded` when state: `succeeded`", func() {
				status := ServiceInstanceStateToStatus(operationType, "succeeded", isUserProvided)
				Expect(status).To(Equal("create succeeded"))
			})

			It("returns status: `create failed` when state: `failed`", func() {
				status := ServiceInstanceStateToStatus(operationType, "failed", isUserProvided)
				Expect(status).To(Equal("create failed"))
			})

			It("returns status: `` when state: ``", func() {
				status := ServiceInstanceStateToStatus(operationType, "", isUserProvided)
				Expect(status).To(Equal(""))
			})
		})
	})

	Context("when the service is user provided", func() {
		isUserProvided := true

		It("returns status: `` when state: ``", func() {
			status := ServiceInstanceStateToStatus(operationType, "", isUserProvided)
			Expect(status).To(Equal(""))
		})
	})
})
<commit_msg>Remove RunCliCommand from service_test<commit_after>package service_test

import (
	"github.com/cloudfoundry/cli/cf/commandregistry"
	"github.com/cloudfoundry/cli/cf/commands/service"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/requirements"
	"github.com/cloudfoundry/cli/flags"

	"github.com/cloudfoundry/cli/cf/requirements/requirementsfakes"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"

	"github.com/cloudfoundry/cli/plugin/models"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("service command", func() {
	var (
		ui                         *testterm.FakeUI
		deps                       commandregistry.Dependency
		flagContext                flags.FlagContext
		reqFactory                 *requirementsfakes.FakeFactory
		loginRequirement           requirements.Requirement
		targetedSpaceRequirement   requirements.Requirement
		serviceInstanceRequirement *requirementsfakes.FakeServiceInstanceRequirement
		pluginCall                 bool

		cmd *service.ShowService
	)

	BeforeEach(func() {
		ui = &testterm.FakeUI{}
		pluginCall = false

		deps = commandregistry.Dependency{
			UI:           ui,
			PluginModels: &commandregistry.PluginModels{},
		}

		cmd = &service.ShowService{}

		flagContext = flags.NewFlagContext(cmd.MetaData().Flags)
		reqFactory = &requirementsfakes.FakeFactory{}

		loginRequirement = &passingRequirement{Name: "login-requirement"}
		reqFactory.NewLoginRequirementReturns(loginRequirement)
		targetedSpaceRequirement = &passingRequirement{Name: "targeted-space-requirement"}
		reqFactory.NewTargetedSpaceRequirementReturns(targetedSpaceRequirement)
		serviceInstanceRequirement = &requirementsfakes.FakeServiceInstanceRequirement{}
		reqFactory.NewServiceInstanceRequirementReturns(serviceInstanceRequirement)
	})

	Describe("Requirements", func() {
		BeforeEach(func() {
			cmd.SetDependency(deps, pluginCall)
		})

		Context("when not provided exactly 1 argument", func() {
			It("fails", func() {
				err := flagContext.Parse("too", "many")
				Expect(err).NotTo(HaveOccurred())
				Expect(func() { cmd.Requirements(reqFactory, flagContext) }).To(Panic())
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Incorrect Usage", "Requires an argument"},
				))
			})
		})

		Context("when provided exactly one arg", func() {
			var actualRequirements []requirements.Requirement

			BeforeEach(func() {
				err := flagContext.Parse("service-name")
				Expect(err).NotTo(HaveOccurred())
				actualRequirements = cmd.Requirements(reqFactory, flagContext)
			})

			It("returns a LoginRequirement", func() {
				Expect(reqFactory.NewLoginRequirementCallCount()).To(Equal(1))
				Expect(actualRequirements).To(ContainElement(loginRequirement))
			})

			It("returns a TargetedSpaceRequirement", func() {
				Expect(reqFactory.NewTargetedSpaceRequirementCallCount()).To(Equal(1))
				Expect(actualRequirements).To(ContainElement(targetedSpaceRequirement))
			})

			It("returns a ServiceInstanceRequirement", func() {
				Expect(reqFactory.NewServiceInstanceRequirementCallCount()).To(Equal(1))
				Expect(actualRequirements).To(ContainElement(serviceInstanceRequirement))
			})
		})
	})

	Describe("Execute", func() {
		var (
			serviceInstance models.ServiceInstance
		)

		BeforeEach(func() {
			serviceInstance = models.ServiceInstance{
				ServiceInstanceFields: models.ServiceInstanceFields{
					GUID: "service1-guid",
					Name: "service1",
					LastOperation: models.LastOperationFields{
						Type:        "create",
						State:       "in progress",
						Description: "creating resource - step 1",
						CreatedAt:   "created-date",
						UpdatedAt:   "updated-date",
					},
					DashboardURL: "some-url",
				},
				ServicePlan: models.ServicePlanFields{
					GUID: "plan-guid",
					Name: "plan-name",
				},
				ServiceOffering: models.ServiceOfferingFields{
					Label:            "mysql",
					DocumentationURL: "http://documentation.url",
					Description:      "the-description",
				},
			}
		})

		JustBeforeEach(func() {
			serviceInstanceRequirement.GetServiceInstanceReturns(serviceInstance)
			cmd.SetDependency(deps, pluginCall)
			cmd.Requirements(reqFactory, flagContext)
			cmd.Execute(flagContext)
		})

		Context("when invoked by a plugin", func() {
			var (
				pluginModel *plugin_models.GetService_Model
			)

			BeforeEach(func() {
				pluginModel = &plugin_models.GetService_Model{}
				deps.PluginModels.Service = pluginModel
				pluginCall = true
				err := flagContext.Parse("service1")
				Expect(err).NotTo(HaveOccurred())
			})

			It("populates the plugin model upon execution", func() {
				Expect(pluginModel.Name).To(Equal("service1"))
				Expect(pluginModel.Guid).To(Equal("service1-guid"))
				Expect(pluginModel.LastOperation.Type).To(Equal("create"))
				Expect(pluginModel.LastOperation.State).To(Equal("in progress"))
				Expect(pluginModel.LastOperation.Description).To(Equal("creating resource - step 1"))
				Expect(pluginModel.LastOperation.CreatedAt).To(Equal("created-date"))
				Expect(pluginModel.LastOperation.UpdatedAt).To(Equal("updated-date"))
				Expect(pluginModel.LastOperation.Type).To(Equal("create"))
				Expect(pluginModel.ServicePlan.Name).To(Equal("plan-name"))
				Expect(pluginModel.ServicePlan.Guid).To(Equal("plan-guid"))
				Expect(pluginModel.ServiceOffering.DocumentationUrl).To(Equal("http://documentation.url"))
				Expect(pluginModel.ServiceOffering.Name).To(Equal("mysql"))
			})
		})

		Context("when the service is externally provided", func() {
			Context("when only the service name is specified", func() {
				BeforeEach(func() {
					err := flagContext.Parse("service1")
					Expect(err).NotTo(HaveOccurred())
				})

				It("shows the service", func() {
					Expect(ui.Outputs).To(ContainSubstrings(
						[]string{"Service instance:", "service1"},
						[]string{"Service: ", "mysql"},
						[]string{"Plan: ", "plan-name"},
						[]string{"Description: ", "the-description"},
						[]string{"Documentation url: ", "http://documentation.url"},
						[]string{"Dashboard: ", "some-url"},
						[]string{"Last Operation"},
						[]string{"Status: ", "create in progress"},
						[]string{"Message: ", "creating resource - step 1"},
						[]string{"Started: ", "created-date"},
						[]string{"Updated: ", "updated-date"},
					))
				})

				Context("when the service instance CreatedAt is empty", func() {
					BeforeEach(func() {
						serviceInstance.LastOperation.CreatedAt = ""
					})

					It("does not output the Started line", func() {
						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Service instance:", "service1"},
							[]string{"Service: ", "mysql"},
							[]string{"Plan: ", "plan-name"},
							[]string{"Description: ", "the-description"},
							[]string{"Documentation url: ", "http://documentation.url"},
							[]string{"Dashboard: ", "some-url"},
							[]string{"Last Operation"},
							[]string{"Status: ", "create in progress"},
							[]string{"Message: ", "creating resource - step 1"},
							[]string{"Updated: ", "updated-date"},
						))
						Expect(ui.Outputs).ToNot(ContainSubstrings(
							[]string{"Started: "},
						))
					})
				})

				Context("when the state is 'in progress'", func() {
					BeforeEach(func() {
						serviceInstance.LastOperation.State = "in progress"
					})

					It("shows status: `create in progress`", func() {
						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Status: ", "create in progress"},
						))
					})
				})

				Context("when the state is 'succeeded'", func() {
					BeforeEach(func() {
						serviceInstance.LastOperation.State = "succeeded"
					})

					It("shows status: `create succeeded`", func() {
						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Status: ", "create succeeded"},
						))
					})
				})

				Context("when the state is 'failed'", func() {
					BeforeEach(func() {
						serviceInstance.LastOperation.State = "failed"
					})

					It("shows status: `create failed`", func() {
						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Status: ", "create failed"},
						))
					})
				})

				Context("when the state is empty", func() {
					BeforeEach(func() {
						serviceInstance.LastOperation.State = ""
					})

					It("shows status: ``", func() {
						Expect(ui.Outputs).To(ContainSubstrings(
							[]string{"Status: ", ""},
						))
					})
				})
			})

			Context("when the guid flag is provided", func() {
				BeforeEach(func() {
					err := flagContext.Parse("--guid", "service1")
					Expect(err).NotTo(HaveOccurred())
				})

				It("shows only the service guid", func() {
					Expect(ui.Outputs).To(ContainSubstrings(
						[]string{"service1-guid"},
					))

					Expect(ui.Outputs).ToNot(ContainSubstrings(
						[]string{"Service instance:", "service1"},
					))
				})
			})
		})

		Context("when the service is user provided", func() {
			BeforeEach(func() {
				serviceInstance = models.ServiceInstance{
					ServiceInstanceFields: models.ServiceInstanceFields{
						Name: "service1",
						GUID: "service1-guid",
					},
				}

				err := flagContext.Parse("service1")
				Expect(err).NotTo(HaveOccurred())
			})

			It("shows user provided services", func() {
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Service instance: ", "service1"},
					[]string{"Service: ", "user-provided"},
				))
			})
		})

		Context("when the service has tags", func() {
			BeforeEach(func() {
				serviceInstance = models.ServiceInstance{
					ServiceInstanceFields: models.ServiceInstanceFields{
						Tags: []string{"tag1", "tag2"},
					},
					ServicePlan: models.ServicePlanFields{GUID: "plan-guid", Name: "plan-name"},
				}

				err := flagContext.Parse("service1")
				Expect(err).NotTo(HaveOccurred())
			})

			It("includes the tags in the output", func() {
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Tags: ", "tag1, tag2"},
				))
			})
		})
	})
})

var _ = Describe("ServiceInstanceStateToStatus", func() {
	var operationType string
	Context("when the service is not user provided", func() {
		isUserProvided := false

		Context("when operationType is `create`", func() {
			BeforeEach(func() { operationType = "create" })

			It("returns status: `create in progress` when state: `in progress`", func() {
				status := service.ServiceInstanceStateToStatus(operationType, "in progress", isUserProvided)
				Expect(status).To(Equal("create in progress"))
			})

			It("returns status: `create succeeded` when state: `succeeded`", func() {
				status := service.ServiceInstanceStateToStatus(operationType, "succeeded", isUserProvided)
				Expect(status).To(Equal("create succeeded"))
			})

			It("returns status: `create failed` when state: `failed`", func() {
				status := service.ServiceInstanceStateToStatus(operationType, "failed", isUserProvided)
				Expect(status).To(Equal("create failed"))
			})

			It("returns status: `` when state: ``", func() {
				status := service.ServiceInstanceStateToStatus(operationType, "", isUserProvided)
				Expect(status).To(Equal(""))
			})
		})
	})

	Context("when the service is user provided", func() {
		isUserProvided := true

		It("returns status: `` when state: ``", func() {
			status := service.ServiceInstanceStateToStatus(operationType, "", isUserProvided)
			Expect(status).To(Equal(""))
		})
	})
})
<|endoftext|>


<commit_before>package rest

import (
	"github.com/gocraft/web"
	"b00lduck/datalogger/dataservice/orm"
	"net/http"
	"io/ioutil"
	"strconv"
)

// Get all flags
func (c *Context) FlagHandler(rw web.ResponseWriter, req *web.Request) {
	var flags []orm.Counter
	db.Find(&flags)
	marshal(rw, flags)
}

// Get specific flag by code
func (c *Context) FlagByCodeHandler(rw web.ResponseWriter, req *web.Request) {

	code := parseStringPathParameter(req, "code")
	var flag orm.Flag
	db.Where(&orm.Flag{Code: code}).First(&flag)

	if (flag.ID == 0) {
		rw.WriteHeader(http.StatusNotFound)
		rw.Write([]byte("Flag not found"))
		return
	}

	marshal(rw, flag)
}

// Chenge flag state by code
func (c *Context) FlagByCodeChangeStateHandler(rw web.ResponseWriter, req *web.Request) {

	code := parseStringPathParameter(req, "code")
	var flag orm.Flag
	db.Where(&orm.Flag{Code: code}).First(&flag)

	if (flag.ID == 0) {
		rw.WriteHeader(http.StatusNotFound)
		rw.Write([]byte("Flag not found"))
		return
	}

	body, err := ioutil.ReadAll(req.Body);
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte("Could not read body"))
		return
	}

	state, err := strconv.Atoi(string(body))
	if err != nil {
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write([]byte("Could not parse state"))
		return
	}

	flagState := orm.NewFlagState(flag, uint8(state))
	db.Create(&flagState)

	flag.State = uint8(state)
	flag.LastChange = flagState.Timestamp
	db.Save(flag)

	marshal(rw, flagState)
}

// Get flag states in a optionally given time range
// Query parameters: start,end
func (c *Context) FlagByCodeGetStatesHandler(rw web.ResponseWriter, req *web.Request) {

	code := parseStringPathParameter(req, "code")
	var flag orm.Flag
	db.Where(&orm.Flag{Code: code}).First(&flag)

	if (flag.ID == 0) {
		rw.WriteHeader(http.StatusNotFound)
		rw.Write([]byte("Flag not found"))
		return
	}

	start,err := c.parseUintQueryParameter(rw, "start")
	if (err != nil) {
		return
	}

	end,err := c.parseUintQueryParameter(rw, "end")
	if (err != nil) {
		return
	}

	var flagReadings []orm.FlagState
	orm.GetOrderedWindowedQuery(db, "flag_id", flag.ID, start, end).Find(&flagReadings)
	marshal(rw, flagReadings)
}<commit_msg>DATASERVICE: flag start/end padding<commit_after>package rest

import (
	"github.com/gocraft/web"
	"b00lduck/datalogger/dataservice/orm"
	"net/http"
	"io/ioutil"
	"strconv"
)

// Get all flags
func (c *Context) FlagHandler(rw web.ResponseWriter, req *web.Request) {
	var flags []orm.Counter
	db.Find(&flags)
	marshal(rw, flags)
}

// Get specific flag by code
func (c *Context) FlagByCodeHandler(rw web.ResponseWriter, req *web.Request) {

	code := parseStringPathParameter(req, "code")
	var flag orm.Flag
	db.Where(&orm.Flag{Code: code}).First(&flag)

	if (flag.ID == 0) {
		rw.WriteHeader(http.StatusNotFound)
		rw.Write([]byte("Flag not found"))
		return
	}

	marshal(rw, flag)
}

// Chenge flag state by code
func (c *Context) FlagByCodeChangeStateHandler(rw web.ResponseWriter, req *web.Request) {

	code := parseStringPathParameter(req, "code")
	var flag orm.Flag
	db.Where(&orm.Flag{Code: code}).First(&flag)

	if (flag.ID == 0) {
		rw.WriteHeader(http.StatusNotFound)
		rw.Write([]byte("Flag not found"))
		return
	}

	body, err := ioutil.ReadAll(req.Body);
	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte("Could not read body"))
		return
	}

	state, err := strconv.Atoi(string(body))
	if err != nil {
		rw.WriteHeader(http.StatusBadRequest)
		rw.Write([]byte("Could not parse state"))
		return
	}

	flagState := orm.NewFlagState(flag, uint8(state))
	db.Create(&flagState)

	flag.State = uint8(state)
	flag.LastChange = flagState.Timestamp
	db.Save(flag)

	marshal(rw, flagState)
}

// Get flag states in a optionally given time range
// Query parameters: start,end
func (c *Context) FlagByCodeGetStatesHandler(rw web.ResponseWriter, req *web.Request) {

	code := parseStringPathParameter(req, "code")
	var flag orm.Flag
	db.Where(&orm.Flag{Code: code}).First(&flag)

	if (flag.ID == 0) {
		rw.WriteHeader(http.StatusNotFound)
		rw.Write([]byte("Flag not found"))
		return
	}

	start,err := c.parseUintQueryParameter(rw, "start")
	if (err != nil) {
		return
	}

	end,err := c.parseUintQueryParameter(rw, "end")
	if (err != nil) {
		return
	}

	var flagStates []orm.FlagState
	orm.GetOrderedWindowedQuery(db, "flag_id", flag.ID, start, end).Find(&flagStates)

	startReading := orm.FlagState{
		State: flagStates[0].State,
		Timestamp: start,
		FlagID: flag.ID,
	}

	endReading := orm.FlagState{
		State: flagStates[len(flagStates)-1].State,
		Timestamp: end,
		FlagID: flag.ID,
	}

	flagStates = append([]orm.FlagState{startReading}, flagStates...)
	flagStates = append(flagStates, endReading)

	marshal(rw, flagStates)
}
<|endoftext|>


<commit_before>package whetstone_test

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"

	"github.com/cloudfoundry-incubator/runtime-schema/models/factories"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var (
	cli    string
	tmpDir string
)

var _ = BeforeSuite(func() {
	tmpDir = os.TempDir()

	var err error
	cli, err = gexec.Build("github.com/pivotal-cf-experimental/lattice-cli/ltc")
	Expect(err).ToNot(HaveOccurred())
})

var _ = AfterSuite(func() {
	gexec.CleanupBuildArtifacts()
})

var _ = Describe("Lattice", func() {
	Context("when desiring a docker-based LRP", func() {

		var (
			appName string
			route   string
		)

		BeforeEach(func() {
			appName = fmt.Sprintf("whetstone-%s", factories.GenerateGuid())
			route = fmt.Sprintf("%s.%s", appName, domain)

			targetLattice(domain, username, password)
		})

		AfterEach(func() {
			removeApp(appName)

			Eventually(errorCheckForRoute(route), timeout, 1).Should(HaveOccurred())
		})

		It("eventually runs a docker app", func() {
			startDockerApp(appName, "-i", "docker:///cloudfoundry/lattice-app", "--env", "APP_NAME", "--", "/lattice-app", "--message", "Hello Whetstone", "--quiet")

			Eventually(errorCheckForRoute(route), timeout, 1).ShouldNot(HaveOccurred())

			logsStream := streamLogs(appName)
			Eventually(logsStream.Out, timeout).Should(gbytes.Say("WHETSTONE TEST APP. Says Hello Whetstone."))

			scaleApp(appName)

			instanceCountChan := make(chan int, numCpu)
			go countInstances(route, instanceCountChan)
			Eventually(instanceCountChan, timeout).Should(Receive(Equal(3)))

			logsStream.Terminate().Wait()
		})

		It("eventually runs a docker app with metadata from Docker Hub", func() {
			startDockerApp(appName, "-i", "docker:///cloudfoundry/lattice-app")

			Eventually(errorCheckForRoute(route), timeout, 1).ShouldNot(HaveOccurred())
		})
	})
})

func startDockerApp(appName string, args ...string) {
	startArgs := append([]string{"start", appName}, args...)
	command := command(cli, startArgs...)
	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

	Expect(err).ToNot(HaveOccurred())
	expectExit(session)

	Expect(session.Out).To(gbytes.Say(appName + " is now running."))
}

func streamLogs(appName string) *gexec.Session {
	command := command(cli, "logs", appName)
	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
	Expect(err).ToNot(HaveOccurred())

	return session
}

func scaleApp(appName string) {
	command := command(cli, "scale", appName, "--instances", "3")
	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

	Expect(err).ToNot(HaveOccurred())
	expectExit(session)
}

func removeApp(appName string) {
	command := command(cli, "remove", appName)
	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

	Expect(err).ToNot(HaveOccurred())
	expectExit(session)
}

func targetLattice(domain, username, password string) {
	stdinReader, stdinWriter := io.Pipe()

	command := command(cli, "target", domain)
	command.Stdin = stdinReader

	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
	Expect(err).ToNot(HaveOccurred())

	if username != "" || password != "" {
		Eventually(session.Out).Should(gbytes.Say("Username: "))
		stdinWriter.Write([]byte(username + "\n"))

		Eventually(session.Out).Should(gbytes.Say("Password: "))
		stdinWriter.Write([]byte(password + "\n"))
	}

	stdinWriter.Close()
	expectExit(session)
}

func command(name string, arg ...string) *exec.Cmd {
	command := exec.Command(name, arg...)

	appName := "APP_NAME=WHETSTONE TEST APP"
	cliHome := fmt.Sprintf("LATTICE_CLI_HOME=%s", tmpDir)
	cliTimeout := fmt.Sprintf("LATTICE_CLI_TIMEOUT=%d", timeout)

	command.Env = []string{cliHome, appName, cliTimeout}
	return command
}

func errorCheckForRoute(route string) func() error {
	return func() error {
		response, err := makeGetRequestToRoute(route)
		if err != nil {
			return err
		}

		io.Copy(ioutil.Discard, response.Body)
		defer response.Body.Close()

		if response.StatusCode != 200 {
			return fmt.Errorf("Status code %d should be 200", response.StatusCode)
		}

		return nil
	}
}

func countInstances(route string, instanceCountChan chan<- int) {
	defer GinkgoRecover()
	instanceIndexRoute := fmt.Sprintf("%s/index", route)
	instancesSeen := make(map[int]bool)

	instanceIndexChan := make(chan int, numCpu)

	for i := 0; i < numCpu; i++ {
		go pollForInstanceIndices(instanceIndexRoute, instanceIndexChan)
	}

	for {
		instanceIndex := <-instanceIndexChan
		instancesSeen[instanceIndex] = true
		instanceCountChan <- len(instancesSeen)
	}
}

func pollForInstanceIndices(route string, instanceIndexChan chan<- int) {
	defer GinkgoRecover()
	for {
		response, err := makeGetRequestToRoute(route)
		Expect(err).To(BeNil())

		responseBody, err := ioutil.ReadAll(response.Body)
		defer response.Body.Close()
		Expect(err).To(BeNil())

		instanceIndex, err := strconv.Atoi(string(responseBody))
		if err != nil {
			continue
		}
		instanceIndexChan <- instanceIndex
	}
}

func makeGetRequestToRoute(route string) (*http.Response, error) {
	routeWithScheme := fmt.Sprintf("http://%s", route)
	resp, err := http.DefaultClient.Get(routeWithScheme)
	if err != nil {
		return nil, err
	}

	return resp, nil
}

func expectExit(session *gexec.Session) {
	Eventually(session, timeout).Should(gexec.Exit(0))
	Expect(string(session.Out.Contents())).To(HaveSuffix("\n"))
}
<commit_msg>Log start commands to ginkgo writer in whetstone<commit_after>package whetstone_test

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"

	"github.com/cloudfoundry-incubator/runtime-schema/models/factories"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var (
	cli    string
	tmpDir string
)

var _ = BeforeSuite(func() {
	tmpDir = os.TempDir()

	var err error
	cli, err = gexec.Build("github.com/pivotal-cf-experimental/lattice-cli/ltc")
	Expect(err).ToNot(HaveOccurred())
})

var _ = AfterSuite(func() {
	gexec.CleanupBuildArtifacts()
})

var _ = Describe("Lattice", func() {
	Context("when desiring a docker-based LRP", func() {

		var (
			appName string
			route   string
		)

		BeforeEach(func() {
			appName = fmt.Sprintf("whetstone-%s", factories.GenerateGuid())
			route = fmt.Sprintf("%s.%s", appName, domain)

			targetLattice(domain, username, password)
		})

		AfterEach(func() {
			removeApp(appName)

			Eventually(errorCheckForRoute(route), timeout, 1).Should(HaveOccurred())
		})

		It("eventually runs a docker app", func() {
			startDockerApp(appName, "-i", "docker:///cloudfoundry/lattice-app", "--env", "APP_NAME", "--", "/lattice-app", "--message", "Hello Whetstone", "--quiet")

			Eventually(errorCheckForRoute(route), timeout, 1).ShouldNot(HaveOccurred())

			logsStream := streamLogs(appName)
			Eventually(logsStream.Out, timeout).Should(gbytes.Say("WHETSTONE TEST APP. Says Hello Whetstone."))

			scaleApp(appName)

			instanceCountChan := make(chan int, numCpu)
			go countInstances(route, instanceCountChan)
			Eventually(instanceCountChan, timeout).Should(Receive(Equal(3)))

			logsStream.Terminate().Wait()
		})

		It("eventually runs a docker app with metadata from Docker Hub", func() {
			startDockerApp(appName, "-i", "docker:///cloudfoundry/lattice-app")

			Eventually(errorCheckForRoute(route), timeout, 1).ShouldNot(HaveOccurred())
		})
	})
})

func startDockerApp(appName string, args ...string) {
	startArgs := append([]string{"start", appName}, args...)
	command := command(cli, startArgs...)
	fmt.Fprintf(GinkgoWriter, "Starting Docker app with command %#v", command)
	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

	Expect(err).ToNot(HaveOccurred())
	expectExit(session)

	Expect(session.Out).To(gbytes.Say(appName + " is now running."))
}

func streamLogs(appName string) *gexec.Session {
	command := command(cli, "logs", appName)
	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
	Expect(err).ToNot(HaveOccurred())

	return session
}

func scaleApp(appName string) {
	command := command(cli, "scale", appName, "--instances", "3")
	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

	Expect(err).ToNot(HaveOccurred())
	expectExit(session)
}

func removeApp(appName string) {
	command := command(cli, "remove", appName)
	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

	Expect(err).ToNot(HaveOccurred())
	expectExit(session)
}

func targetLattice(domain, username, password string) {
	stdinReader, stdinWriter := io.Pipe()

	command := command(cli, "target", domain)
	command.Stdin = stdinReader

	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
	Expect(err).ToNot(HaveOccurred())

	if username != "" || password != "" {
		Eventually(session.Out).Should(gbytes.Say("Username: "))
		stdinWriter.Write([]byte(username + "\n"))

		Eventually(session.Out).Should(gbytes.Say("Password: "))
		stdinWriter.Write([]byte(password + "\n"))
	}

	stdinWriter.Close()
	expectExit(session)
}

func command(name string, arg ...string) *exec.Cmd {
	command := exec.Command(name, arg...)

	appName := "APP_NAME=WHETSTONE TEST APP"
	cliHome := fmt.Sprintf("LATTICE_CLI_HOME=%s", tmpDir)
	cliTimeout := fmt.Sprintf("LATTICE_CLI_TIMEOUT=%d", timeout)

	command.Env = []string{cliHome, appName, cliTimeout}
	return command
}

func errorCheckForRoute(route string) func() error {
	return func() error {
		response, err := makeGetRequestToRoute(route)
		if err != nil {
			return err
		}

		io.Copy(ioutil.Discard, response.Body)
		defer response.Body.Close()

		if response.StatusCode != 200 {
			return fmt.Errorf("Status code %d should be 200", response.StatusCode)
		}

		return nil
	}
}

func countInstances(route string, instanceCountChan chan<- int) {
	defer GinkgoRecover()
	instanceIndexRoute := fmt.Sprintf("%s/index", route)
	instancesSeen := make(map[int]bool)

	instanceIndexChan := make(chan int, numCpu)

	for i := 0; i < numCpu; i++ {
		go pollForInstanceIndices(instanceIndexRoute, instanceIndexChan)
	}

	for {
		instanceIndex := <-instanceIndexChan
		instancesSeen[instanceIndex] = true
		instanceCountChan <- len(instancesSeen)
	}
}

func pollForInstanceIndices(route string, instanceIndexChan chan<- int) {
	defer GinkgoRecover()
	for {
		response, err := makeGetRequestToRoute(route)
		Expect(err).To(BeNil())

		responseBody, err := ioutil.ReadAll(response.Body)
		defer response.Body.Close()
		Expect(err).To(BeNil())

		instanceIndex, err := strconv.Atoi(string(responseBody))
		if err != nil {
			continue
		}
		instanceIndexChan <- instanceIndex
	}
}

func makeGetRequestToRoute(route string) (*http.Response, error) {
	routeWithScheme := fmt.Sprintf("http://%s", route)
	resp, err := http.DefaultClient.Get(routeWithScheme)
	if err != nil {
		return nil, err
	}

	return resp, nil
}

func expectExit(session *gexec.Session) {
	Eventually(session, timeout).Should(gexec.Exit(0))
	Expect(string(session.Out.Contents())).To(HaveSuffix("\n"))
}
<|endoftext|>