<commit_before>package frontends

import (
	"bytes"
	"context"
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"regexp"
	"time"

	"github.com/blang/semver"
	"github.com/openshift/origin/pkg/monitor/monitorapi"

	"github.com/onsi/ginkgo"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/events"
	"k8s.io/kubernetes/test/e2e/framework"
	"k8s.io/kubernetes/test/e2e/upgrades"

	routeclientset "github.com/openshift/client-go/route/clientset/versioned"
	"github.com/openshift/origin/pkg/monitor"
	"github.com/openshift/origin/test/extended/util"
	"github.com/openshift/origin/test/extended/util/disruption"
)

// AvailableTest tests that route frontends are available before, during, and
// after a cluster upgrade.
type AvailableTest struct {
	frontends []Frontend
}

type Frontend struct {
	Namespace string
	Name      string
	URL       string
	Path      string

	Expect       string
	ExpectRegexp *regexp.Regexp
}

func (AvailableTest) Name() string { return "frontend-ingress-available" }
func (AvailableTest) DisplayName() string {
	return "[sig-network-edge] Cluster frontend ingress remain available"
}

// Setup finds the routes the platform exposes by default
func (t *AvailableTest) Setup(f *framework.Framework) {
	t.frontends = []Frontend{
		{Namespace: "openshift-authentication", Name: "oauth-openshift", Path: "/healthz", Expect: "ok"},
		{Namespace: "openshift-console", Name: "console", ExpectRegexp: regexp.MustCompile(`(Red Hat OpenShift Container Platform|OKD)`)},
	}
	config, err := framework.LoadConfig()
	framework.ExpectNoError(err)
	client, err := routeclientset.NewForConfig(config)
	framework.ExpectNoError(err)
	for i, frontend := range t.frontends {
		route, err := client.RouteV1().Routes(frontend.Namespace).Get(context.Background(), frontend.Name, metav1.GetOptions{})
		framework.ExpectNoError(err)
		for _, ingress := range route.Status.Ingress {
			if len(ingress.Host) > 0 {
				t.frontends[i].URL = fmt.Sprintf("https://%s", ingress.Host)

				break
			}
		}
		if len(t.frontends[i].URL) == 0 {
			framework.Failf("route %s/%s has no ingress host: %#v", route.Namespace, route.Name, route.Status.Ingress)
		}
	}
}

// Test runs a connectivity check to the service.
func (t *AvailableTest) Test(f *framework.Framework, done <-chan struct{}, upgrade upgrades.UpgradeType) {
	config, err := framework.LoadConfig()
	framework.ExpectNoError(err)
	client, err := framework.LoadClientset()
	framework.ExpectNoError(err)

	stopCh := make(chan struct{})
	defer close(stopCh)
	newBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()})
	r := newBroadcaster.NewRecorder(scheme.Scheme, "openshift.io/frontends-available-test")
	newBroadcaster.StartRecordingToSink(stopCh)

	ginkgo.By("continuously hitting infrastructure through the router")

	ctx, cancel := context.WithCancel(context.Background())
	m := monitor.NewMonitorWithInterval(1 * time.Second)
	for _, frontend := range t.frontends {
		err = startEndpointMonitoring(ctx, m, frontend, r)
		framework.ExpectNoError(err, "unable to monitor route")
	}

	start := time.Now()
	m.StartSampling(ctx)

	// wait to ensure API is still up after the test ends
	<-done
	ginkgo.By("waiting for any post disruption failures")
	time.Sleep(15 * time.Second)
	cancel()
	end := time.Now()

	// starting from 4.8, enforce the requirement that frontends remains available
	hasAllFixes, err := util.AllClusterVersionsAreGTE(semver.Version{Major: 4, Minor: 8}, config)
	if err != nil {
		framework.Logf("Cannot require full control plane availability, some versions could not be checked: %v", err)
	}

	toleratedDisruption := 0.20
	switch {
	case framework.ProviderIs("azure"), framework.ProviderIs("aws"), framework.ProviderIs("gce"):
		if hasAllFixes {
			framework.Logf("Cluster contains no versions older than 4.8, tolerating no disruption")
			toleratedDisruption = 0
		}
	}
	disruption.ExpectNoDisruption(f, toleratedDisruption, end.Sub(start), m.Intervals(time.Time{}, time.Time{}), "Frontends were unreachable during disruption")
}

// Teardown cleans up any remaining resources.
func (t *AvailableTest) Teardown(f *framework.Framework) {
	// rely on the namespace deletion to clean up everything
}

func startEndpointMonitoring(ctx context.Context, m *monitor.Monitor, frontend Frontend, r events.EventRecorder) error {
	// this client reuses connections and detects abrupt breaks
	continuousClient, err := rest.UnversionedRESTClientFor(&rest.Config{
		Host:    frontend.URL,
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: 15 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 15 * time.Second,
			TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
		},
		ContentConfig: rest.ContentConfig{
			NegotiatedSerializer: runtime.NewSimpleNegotiatedSerializer(scheme.Codecs.SupportedMediaTypes()[0]),
		},
	})
	if err != nil {
		return err
	}
	go monitor.NewSampler(m, time.Second, func(previous bool) (condition *monitorapi.Condition, next bool) {
		data, err := continuousClient.Get().AbsPath(frontend.Path).DoRaw(ctx)
		if err == nil && !bytes.Contains(data, []byte(frontend.Expect)) {
			err = fmt.Errorf("route returned success but did not contain the correct body contents: %q", string(data))
		}
		switch {
		case err == nil && !previous:
			condition = &monitorapi.Condition{
				Level:   monitorapi.Info,
				Locator: locateRoute(frontend.Namespace, frontend.Name),
				Message: "Route started responding to GET requests on reused connections",
			}
		case err != nil && previous:
			framework.Logf("Route %s is unreachable on reused connections: %v", frontend.Name, err)
			r.Eventf(&v1.ObjectReference{Kind: "Route", Namespace: "kube-system", Name: frontend.Name}, nil, v1.EventTypeWarning, "Unreachable", "detected", "on reused connections")
			condition = &monitorapi.Condition{
				Level:   monitorapi.Error,
				Locator: locateRoute(frontend.Namespace, frontend.Name),
				Message: "Route stopped responding to GET requests on reused connections",
			}
		case err != nil:
			framework.Logf("Route %s is unreachable on reused connections: %v", frontend.Name, err)
		}
		return condition, err == nil
	}).WhenFailing(ctx, &monitorapi.Condition{
		Level:   monitorapi.Error,
		Locator: locateRoute(frontend.Namespace, frontend.Name),
		Message: "Route is not responding to GET requests on reused connections",
	})

	// this client creates fresh connections and detects failure to establish connections
	client, err := rest.UnversionedRESTClientFor(&rest.Config{
		Host:    frontend.URL,
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout:   15 * time.Second,
				KeepAlive: -1,
			}).Dial,
			TLSHandshakeTimeout: 15 * time.Second,
			IdleConnTimeout:     15 * time.Second,
			DisableKeepAlives:   true,
			TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
		},
		ContentConfig: rest.ContentConfig{
			NegotiatedSerializer: runtime.NewSimpleNegotiatedSerializer(scheme.Codecs.SupportedMediaTypes()[0]),
		},
	})
	if err != nil {
		return err
	}
	go monitor.NewSampler(m, time.Second, func(previous bool) (condition *monitorapi.Condition, next bool) {
		data, err := client.Get().AbsPath(frontend.Path).DoRaw(ctx)
		switch {
		case err == nil && len(frontend.Expect) != 0 && !bytes.Contains(data, []byte(frontend.Expect)):
			err = fmt.Errorf("route returned success but did not contain the correct body contents: %q", string(data))
		case err == nil && frontend.ExpectRegexp != nil && !frontend.ExpectRegexp.MatchString(string(data)):
			err = fmt.Errorf("route returned success but did not contain the correct body contents: %q", string(data))
		}
		switch {
		case err == nil && !previous:
			condition = &monitorapi.Condition{
				Level:   monitorapi.Info,
				Locator: locateRoute(frontend.Namespace, frontend.Name),
				Message: "Route started responding to GET requests over new connections",
			}
		case err != nil && previous:
			framework.Logf("Route %s is unreachable on new connections: %v", frontend.Name, err)
			r.Eventf(&v1.ObjectReference{Kind: "Route", Namespace: "kube-system", Name: frontend.Name}, nil, v1.EventTypeWarning, "Unreachable", "detected", "on new connections")
			condition = &monitorapi.Condition{
				Level:   monitorapi.Error,
				Locator: locateRoute(frontend.Namespace, frontend.Name),
				Message: "Route stopped responding to GET requests over new connections",
			}
		case err != nil:
			framework.Logf("Route %s is unreachable on new connections: %v", frontend.Name, err)
		}
		return condition, err == nil
	}).WhenFailing(ctx, &monitorapi.Condition{
		Level:   monitorapi.Error,
		Locator: locateRoute(frontend.Namespace, frontend.Name),
		Message: "Route is not responding to GET requests over new connections",
	})
	return nil
}

func locateRoute(ns, name string) string {
	return fmt.Sprintf("ns/%s route/%s", ns, name)
}
<commit_msg>disruption: tolerate on gcp for frontend<commit_after>package frontends

import (
	"bytes"
	"context"
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"regexp"
	"time"

	"github.com/blang/semver"
	"github.com/openshift/origin/pkg/monitor/monitorapi"

	"github.com/onsi/ginkgo"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/events"
	"k8s.io/kubernetes/test/e2e/framework"
	"k8s.io/kubernetes/test/e2e/upgrades"

	routeclientset "github.com/openshift/client-go/route/clientset/versioned"
	"github.com/openshift/origin/pkg/monitor"
	"github.com/openshift/origin/test/extended/util"
	"github.com/openshift/origin/test/extended/util/disruption"
)

// AvailableTest tests that route frontends are available before, during, and
// after a cluster upgrade.
type AvailableTest struct {
	frontends []Frontend
}

type Frontend struct {
	Namespace string
	Name      string
	URL       string
	Path      string

	Expect       string
	ExpectRegexp *regexp.Regexp
}

func (AvailableTest) Name() string { return "frontend-ingress-available" }
func (AvailableTest) DisplayName() string {
	return "[sig-network-edge] Cluster frontend ingress remain available"
}

// Setup finds the routes the platform exposes by default
func (t *AvailableTest) Setup(f *framework.Framework) {
	t.frontends = []Frontend{
		{Namespace: "openshift-authentication", Name: "oauth-openshift", Path: "/healthz", Expect: "ok"},
		{Namespace: "openshift-console", Name: "console", ExpectRegexp: regexp.MustCompile(`(Red Hat OpenShift Container Platform|OKD)`)},
	}
	config, err := framework.LoadConfig()
	framework.ExpectNoError(err)
	client, err := routeclientset.NewForConfig(config)
	framework.ExpectNoError(err)
	for i, frontend := range t.frontends {
		route, err := client.RouteV1().Routes(frontend.Namespace).Get(context.Background(), frontend.Name, metav1.GetOptions{})
		framework.ExpectNoError(err)
		for _, ingress := range route.Status.Ingress {
			if len(ingress.Host) > 0 {
				t.frontends[i].URL = fmt.Sprintf("https://%s", ingress.Host)

				break
			}
		}
		if len(t.frontends[i].URL) == 0 {
			framework.Failf("route %s/%s has no ingress host: %#v", route.Namespace, route.Name, route.Status.Ingress)
		}
	}
}

// Test runs a connectivity check to the service.
func (t *AvailableTest) Test(f *framework.Framework, done <-chan struct{}, upgrade upgrades.UpgradeType) {
	config, err := framework.LoadConfig()
	framework.ExpectNoError(err)
	client, err := framework.LoadClientset()
	framework.ExpectNoError(err)

	stopCh := make(chan struct{})
	defer close(stopCh)
	newBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()})
	r := newBroadcaster.NewRecorder(scheme.Scheme, "openshift.io/frontends-available-test")
	newBroadcaster.StartRecordingToSink(stopCh)

	ginkgo.By("continuously hitting infrastructure through the router")

	ctx, cancel := context.WithCancel(context.Background())
	m := monitor.NewMonitorWithInterval(1 * time.Second)
	for _, frontend := range t.frontends {
		err = startEndpointMonitoring(ctx, m, frontend, r)
		framework.ExpectNoError(err, "unable to monitor route")
	}

	start := time.Now()
	m.StartSampling(ctx)

	// wait to ensure API is still up after the test ends
	<-done
	ginkgo.By("waiting for any post disruption failures")
	time.Sleep(15 * time.Second)
	cancel()
	end := time.Now()

	// starting from 4.8, enforce the requirement that frontends remains available
	hasAllFixes, err := util.AllClusterVersionsAreGTE(semver.Version{Major: 4, Minor: 8}, config)
	if err != nil {
		framework.Logf("Cannot require full control plane availability, some versions could not be checked: %v", err)
	}

	toleratedDisruption := 0.20
	switch {
	// framework.ProviderIs("gce") removed here in 4.9 due to regression. BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1981872
	case framework.ProviderIs("azure"), framework.ProviderIs("aws"):
		if hasAllFixes {
			framework.Logf("Cluster contains no versions older than 4.8, tolerating no disruption")
			toleratedDisruption = 0
		}
	}
	disruption.ExpectNoDisruption(f, toleratedDisruption, end.Sub(start), m.Intervals(time.Time{}, time.Time{}), "Frontends were unreachable during disruption")
}

// Teardown cleans up any remaining resources.
func (t *AvailableTest) Teardown(f *framework.Framework) {
	// rely on the namespace deletion to clean up everything
}

func startEndpointMonitoring(ctx context.Context, m *monitor.Monitor, frontend Frontend, r events.EventRecorder) error {
	// this client reuses connections and detects abrupt breaks
	continuousClient, err := rest.UnversionedRESTClientFor(&rest.Config{
		Host:    frontend.URL,
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: 15 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 15 * time.Second,
			TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
		},
		ContentConfig: rest.ContentConfig{
			NegotiatedSerializer: runtime.NewSimpleNegotiatedSerializer(scheme.Codecs.SupportedMediaTypes()[0]),
		},
	})
	if err != nil {
		return err
	}
	go monitor.NewSampler(m, time.Second, func(previous bool) (condition *monitorapi.Condition, next bool) {
		data, err := continuousClient.Get().AbsPath(frontend.Path).DoRaw(ctx)
		if err == nil && !bytes.Contains(data, []byte(frontend.Expect)) {
			err = fmt.Errorf("route returned success but did not contain the correct body contents: %q", string(data))
		}
		switch {
		case err == nil && !previous:
			condition = &monitorapi.Condition{
				Level:   monitorapi.Info,
				Locator: locateRoute(frontend.Namespace, frontend.Name),
				Message: "Route started responding to GET requests on reused connections",
			}
		case err != nil && previous:
			framework.Logf("Route %s is unreachable on reused connections: %v", frontend.Name, err)
			r.Eventf(&v1.ObjectReference{Kind: "Route", Namespace: "kube-system", Name: frontend.Name}, nil, v1.EventTypeWarning, "Unreachable", "detected", "on reused connections")
			condition = &monitorapi.Condition{
				Level:   monitorapi.Error,
				Locator: locateRoute(frontend.Namespace, frontend.Name),
				Message: "Route stopped responding to GET requests on reused connections",
			}
		case err != nil:
			framework.Logf("Route %s is unreachable on reused connections: %v", frontend.Name, err)
		}
		return condition, err == nil
	}).WhenFailing(ctx, &monitorapi.Condition{
		Level:   monitorapi.Error,
		Locator: locateRoute(frontend.Namespace, frontend.Name),
		Message: "Route is not responding to GET requests on reused connections",
	})

	// this client creates fresh connections and detects failure to establish connections
	client, err := rest.UnversionedRESTClientFor(&rest.Config{
		Host:    frontend.URL,
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout:   15 * time.Second,
				KeepAlive: -1,
			}).Dial,
			TLSHandshakeTimeout: 15 * time.Second,
			IdleConnTimeout:     15 * time.Second,
			DisableKeepAlives:   true,
			TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
		},
		ContentConfig: rest.ContentConfig{
			NegotiatedSerializer: runtime.NewSimpleNegotiatedSerializer(scheme.Codecs.SupportedMediaTypes()[0]),
		},
	})
	if err != nil {
		return err
	}
	go monitor.NewSampler(m, time.Second, func(previous bool) (condition *monitorapi.Condition, next bool) {
		data, err := client.Get().AbsPath(frontend.Path).DoRaw(ctx)
		switch {
		case err == nil && len(frontend.Expect) != 0 && !bytes.Contains(data, []byte(frontend.Expect)):
			err = fmt.Errorf("route returned success but did not contain the correct body contents: %q", string(data))
		case err == nil && frontend.ExpectRegexp != nil && !frontend.ExpectRegexp.MatchString(string(data)):
			err = fmt.Errorf("route returned success but did not contain the correct body contents: %q", string(data))
		}
		switch {
		case err == nil && !previous:
			condition = &monitorapi.Condition{
				Level:   monitorapi.Info,
				Locator: locateRoute(frontend.Namespace, frontend.Name),
				Message: "Route started responding to GET requests over new connections",
			}
		case err != nil && previous:
			framework.Logf("Route %s is unreachable on new connections: %v", frontend.Name, err)
			r.Eventf(&v1.ObjectReference{Kind: "Route", Namespace: "kube-system", Name: frontend.Name}, nil, v1.EventTypeWarning, "Unreachable", "detected", "on new connections")
			condition = &monitorapi.Condition{
				Level:   monitorapi.Error,
				Locator: locateRoute(frontend.Namespace, frontend.Name),
				Message: "Route stopped responding to GET requests over new connections",
			}
		case err != nil:
			framework.Logf("Route %s is unreachable on new connections: %v", frontend.Name, err)
		}
		return condition, err == nil
	}).WhenFailing(ctx, &monitorapi.Condition{
		Level:   monitorapi.Error,
		Locator: locateRoute(frontend.Namespace, frontend.Name),
		Message: "Route is not responding to GET requests over new connections",
	})
	return nil
}

func locateRoute(ns, name string) string {
	return fmt.Sprintf("ns/%s route/%s", ns, name)
}
<|endoftext|>


<commit_before>/* Test the httpstress.Test() function. */
package httpstress

/* Copyright 2014 Chai Chillum

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. */

import "testing"

func TestTest(t *testing.T) {
	up := []string{"https://google.com", "http://google.com"} // These URLs should pass.
	down := []string{"http://test.invalid"}                   // This should fail.
	error := []string{"ftp://localhost"}                      // Error. Non HTTP/HTTPS URL.

	if _, err := Test(1, 1, error); err == nil {
		t.Errorf("%s is ok (should be an error)", error)
	}

	if err, _ := Test(1, 1, up); len(err) > 0 {
		t.Errorf("%s is down (should be up)", up)
	}

	if err, _ := Test(1, 1, down); len(err) == 0 {
		t.Errorf("%s is up (should be down)", down)
	}
}
<commit_msg>Rename `error` variable<commit_after>/* Test the httpstress.Test() function. */
package httpstress

/* Copyright 2014 Chai Chillum

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. */

import "testing"

func TestTest(t *testing.T) {
	up := []string{"https://google.com", "http://google.com"} // These URLs should pass.
	down := []string{"http://test.invalid"}                   // This should fail.
	invalid := []string{"ftp://localhost"}                    // Error. Non HTTP/HTTPS URL.

	if _, err := Test(1, 1, invalid); err == nil {
		t.Errorf("%s is ok (should be an error)", invalid)
	}

	if err, _ := Test(1, 1, up); len(err) > 0 {
		t.Errorf("%s is down (should be up)", up)
	}

	if err, _ := Test(1, 1, down); len(err) == 0 {
		t.Errorf("%s is up (should be down)", down)
	}
}
<|endoftext|>


<commit_before>package docker

import (
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/pkg/errors"
)

// This code is taken from github.com/docker/volume/mounts/windows_parser.go
// See https://github.com/moby/moby/blob/master/LICENSE for the license, Apache License 2.0 at this time.

const (
	// Spec should be in the format [source:]destination[:mode]
	//
	// Examples: c:\foo bar:d:rw
	//           c:\foo:d:\bar
	//           myname:d:
	//           d:\
	//
	// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
	// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
	// test is https://regex-golang.appspot.com/assets/html/index.html
	//
	// Useful link for referencing named capturing groups:
	// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
	//
	// There are three match groups: source, destination and mode.
	//

	// rxHostDir is the first option of a source
	rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*`
	// rxName is the second option of a source
	rxName = `[^\\/:*?"<>|\r\n]+\/?.*`

	// RXReservedNames are reserved names not possible on Windows
	rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`

	// rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
	rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
	// rxSource is the combined possibilities for a source
	rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?`

	// Source. Can be either a host directory, a name, or omitted:
	//  HostDir:
	//    -  Essentially using the folder solution from
	//       https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
	//       but adding case insensitivity.
	//    -  Must be an absolute path such as c:\path
	//    -  Can include spaces such as `c:\program files`
	//    -  And then followed by a colon which is not in the capture group
	//    -  And can be optional
	//  Name:
	//    -  Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
	//    -  And then followed by a colon which is not in the capture group
	//    -  And can be optional

	// rxDestination is the regex expression for the mount destination
	rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `)|([/].*))`

	// Destination (aka container path):
	//    -  Variation on hostdir but can be a drive followed by colon as well
	//    -  If a path, must be absolute. Can include spaces
	//    -  Drive cannot be c: (explicitly checked in code, not RegEx)

	// rxMode is the regex expression for the mode of the mount
	// Mode (optional):
	//    -  Hopefully self explanatory in comparison to above regex's.
	//    -  Colon is not in the capture group
	rxMode = `(:(?P<mode>(?i)ro|rw))?`
)

func errInvalidSpec(spec string) error {
	return errors.Errorf("invalid volume specification: '%s'", spec)
}

type fileInfoProvider interface {
	fileInfo(path string) (exist, isDir bool, err error)
}

type defaultFileInfoProvider struct {
}

func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
	fi, err := os.Stat(path)
	if err != nil {
		if !os.IsNotExist(err) {
			return false, false, err
		}
		return false, false, nil
	}
	return true, fi.IsDir(), nil
}

var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}

func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
	specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`)
	match := specExp.FindStringSubmatch(strings.ToLower(raw))

	// Must have something back
	if len(match) == 0 {
		return nil, errInvalidSpec(raw)
	}

	var split []string
	matchgroups := make(map[string]string)
	// Pull out the sub expressions from the named capture groups
	for i, name := range specExp.SubexpNames() {
		matchgroups[name] = strings.ToLower(match[i])
	}
	if source, exists := matchgroups["source"]; exists {
		if source != "" {
			split = append(split, source)
		}
	}
	if destination, exists := matchgroups["destination"]; exists {
		if destination != "" {
			split = append(split, destination)
		}
	}
	if mode, exists := matchgroups["mode"]; exists {
		if mode != "" {
			split = append(split, mode)
		}
	}
	// Fix #26329. If the destination appears to be a file, and the source is null,
	// it may be because we've fallen through the possible naming regex and hit a
	// situation where the user intention was to map a file into a container through
	// a local volume, but this is not supported by the platform.
	if matchgroups["source"] == "" && matchgroups["destination"] != "" {
		volExp := regexp.MustCompile(`^` + rxName + `$`)
		reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`)

		if volExp.MatchString(matchgroups["destination"]) {
			if reservedNameExp.MatchString(matchgroups["destination"]) {
				return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
			}
		} else {

			exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"])
			if exists && !isDir {
				return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])

			}
		}
	}
	return split, nil
}
<commit_msg>Revert "driver/docker: support unix destination mount path in windows"<commit_after>package docker

import (
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/pkg/errors"
)

// This code is taken from github.com/docker/volume/mounts/windows_parser.go
// See https://github.com/moby/moby/blob/master/LICENSE for the license, Apache License 2.0 at this time.

const (
	// Spec should be in the format [source:]destination[:mode]
	//
	// Examples: c:\foo bar:d:rw
	//           c:\foo:d:\bar
	//           myname:d:
	//           d:\
	//
	// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
	// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
	// test is https://regex-golang.appspot.com/assets/html/index.html
	//
	// Useful link for referencing named capturing groups:
	// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
	//
	// There are three match groups: source, destination and mode.
	//

	// rxHostDir is the first option of a source
	rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*`
	// rxName is the second option of a source
	rxName = `[^\\/:*?"<>|\r\n]+`

	// RXReservedNames are reserved names not possible on Windows
	rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`

	// rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
	rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
	// rxSource is the combined possibilities for a source
	rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?`

	// Source. Can be either a host directory, a name, or omitted:
	//  HostDir:
	//    -  Essentially using the folder solution from
	//       https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
	//       but adding case insensitivity.
	//    -  Must be an absolute path such as c:\path
	//    -  Can include spaces such as `c:\program files`
	//    -  And then followed by a colon which is not in the capture group
	//    -  And can be optional
	//  Name:
	//    -  Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
	//    -  And then followed by a colon which is not in the capture group
	//    -  And can be optional

	// rxDestination is the regex expression for the mount destination
	rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `))`

	// Destination (aka container path):
	//    -  Variation on hostdir but can be a drive followed by colon as well
	//    -  If a path, must be absolute. Can include spaces
	//    -  Drive cannot be c: (explicitly checked in code, not RegEx)

	// rxMode is the regex expression for the mode of the mount
	// Mode (optional):
	//    -  Hopefully self explanatory in comparison to above regex's.
	//    -  Colon is not in the capture group
	rxMode = `(:(?P<mode>(?i)ro|rw))?`
)

func errInvalidSpec(spec string) error {
	return errors.Errorf("invalid volume specification: '%s'", spec)
}

type fileInfoProvider interface {
	fileInfo(path string) (exist, isDir bool, err error)
}

type defaultFileInfoProvider struct {
}

func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
	fi, err := os.Stat(path)
	if err != nil {
		if !os.IsNotExist(err) {
			return false, false, err
		}
		return false, false, nil
	}
	return true, fi.IsDir(), nil
}

var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}

func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
	specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`)
	match := specExp.FindStringSubmatch(strings.ToLower(raw))

	// Must have something back
	if len(match) == 0 {
		return nil, errInvalidSpec(raw)
	}

	var split []string
	matchgroups := make(map[string]string)
	// Pull out the sub expressions from the named capture groups
	for i, name := range specExp.SubexpNames() {
		matchgroups[name] = strings.ToLower(match[i])
	}
	if source, exists := matchgroups["source"]; exists {
		if source != "" {
			split = append(split, source)
		}
	}
	if destination, exists := matchgroups["destination"]; exists {
		if destination != "" {
			split = append(split, destination)
		}
	}
	if mode, exists := matchgroups["mode"]; exists {
		if mode != "" {
			split = append(split, mode)
		}
	}
	// Fix #26329. If the destination appears to be a file, and the source is null,
	// it may be because we've fallen through the possible naming regex and hit a
	// situation where the user intention was to map a file into a container through
	// a local volume, but this is not supported by the platform.
	if matchgroups["source"] == "" && matchgroups["destination"] != "" {
		volExp := regexp.MustCompile(`^` + rxName + `$`)
		reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`)

		if volExp.MatchString(matchgroups["destination"]) {
			if reservedNameExp.MatchString(matchgroups["destination"]) {
				return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
			}
		} else {

			exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"])
			if exists && !isDir {
				return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])

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


<commit_before>package aws

import (
	"fmt"
	"log"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSVolumeAttachment_basic(t *testing.T) {
	var i ec2.Instance
	var v ec2.Volume
	resourceName := "aws_volume_attachment.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfig,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "device_name", "/dev/sdh"),
					testAccCheckInstanceExists("aws_instance.test", &i),
					testAccCheckVolumeExists("aws_ebs_volume.test", &v),
					testAccCheckVolumeAttachmentExists(resourceName, &i, &v),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSVolumeAttachment_skipDestroy(t *testing.T) {
	var i ec2.Instance
	var v ec2.Volume
	resourceName := "aws_volume_attachment.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfigSkipDestroy,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "device_name", "/dev/sdh"),
					testAccCheckInstanceExists("aws_instance.test", &i),
					testAccCheckVolumeExists("aws_ebs_volume.test", &v),
					testAccCheckVolumeAttachmentExists(resourceName, &i, &v),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
				ImportStateVerifyIgnore: []string{
					"skip_destroy", // attribute only used on resource deletion
				},
			},
		},
	})
}

func TestAccAWSVolumeAttachment_attachStopped(t *testing.T) {
	var i ec2.Instance
	var v ec2.Volume
	resourceName := "aws_volume_attachment.test"

	stopInstance := func() {
		conn := testAccProvider.Meta().(*AWSClient).ec2conn

		_, err := conn.StopInstances(&ec2.StopInstancesInput{
			InstanceIds: []*string{i.InstanceId},
		})
		if err != nil {
			t.Fatalf("error stopping instance (%s): %s", aws.StringValue(i.InstanceId), err)
		}

		stateConf := &resource.StateChangeConf{
			Pending:    []string{ec2.InstanceStateNamePending, ec2.InstanceStateNameRunning, ec2.InstanceStateNameStopping},
			Target:     []string{ec2.InstanceStateNameStopped},
			Refresh:    InstanceStateRefreshFunc(conn, *i.InstanceId, []string{}),
			Timeout:    10 * time.Minute,
			Delay:      10 * time.Second,
			MinTimeout: 3 * time.Second,
		}

		_, err = stateConf.WaitForState()
		if err != nil {
			t.Fatalf("Error waiting for instance(%s) to stop: %s", *i.InstanceId, err)
		}
	}

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfigInstanceOnly,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckInstanceExists("aws_instance.test", &i),
				),
			},
			{
				PreConfig: stopInstance,
				Config:    testAccVolumeAttachmentConfig,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "device_name", "/dev/sdh"),
					testAccCheckInstanceExists("aws_instance.test", &i),
					testAccCheckVolumeExists("aws_ebs_volume.test", &v),
					testAccCheckVolumeAttachmentExists(resourceName, &i, &v),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSVolumeAttachment_update(t *testing.T) {
	resourceName := "aws_volume_attachment.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfig_update(false),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "force_detach", "false"),
					resource.TestCheckResourceAttr(resourceName, "skip_destroy", "false"),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
				ImportStateVerifyIgnore: []string{
					"force_detach", // attribute only used on resource deletion
					"skip_destroy", // attribute only used on resource deletion
				},
			},
			{
				Config: testAccVolumeAttachmentConfig_update(true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "force_detach", "true"),
					resource.TestCheckResourceAttr(resourceName, "skip_destroy", "true"),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
				ImportStateVerifyIgnore: []string{
					"force_detach", // attribute only used on resource deletion
					"skip_destroy", // attribute only used on resource deletion
				},
			},
		},
	})
}

func TestAccAWSVolumeAttachment_disappears(t *testing.T) {
	var i ec2.Instance
	var v ec2.Volume
	resourceName := "aws_volume_attachment.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckVolumeAttachmentExists(resourceName, &i, &v),
					testAccCheckVolumeAttachmentDisappears(resourceName, &i, &v),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func testAccCheckVolumeAttachmentExists(n string, i *ec2.Instance, v *ec2.Volume) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		for _, b := range i.BlockDeviceMappings {
			if rs.Primary.Attributes["device_name"] == *b.DeviceName {
				if b.Ebs.VolumeId != nil &&
					rs.Primary.Attributes["volume_id"] == *b.Ebs.VolumeId &&
					rs.Primary.Attributes["volume_id"] == *v.VolumeId {
					// pass
					return nil
				}
			}
		}

		return fmt.Errorf("Error finding instance/volume")
	}
}

func testAccCheckVolumeAttachmentDisappears(n string, i *ec2.Instance, v *ec2.Volume) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).ec2conn

		opts := &ec2.DetachVolumeInput{
			Device:     aws.String(rs.Primary.Attributes["device_name"]),
			InstanceId: i.InstanceId,
			VolumeId:   v.VolumeId,
			Force:      aws.Bool(true),
		}

		_, err := conn.DetachVolume(opts)
		if err != nil {
			return err
		}

		vId := aws.StringValue(v.VolumeId)
		iId := aws.StringValue(i.InstanceId)

		stateConf := &resource.StateChangeConf{
			Pending:    []string{ec2.VolumeAttachmentStateDetaching},
			Target:     []string{ec2.VolumeAttachmentStateDetached},
			Refresh:    volumeAttachmentStateRefreshFunc(conn, rs.Primary.Attributes["device_name"], vId, iId),
			Timeout:    5 * time.Minute,
			Delay:      10 * time.Second,
			MinTimeout: 3 * time.Second,
		}

		log.Printf("[DEBUG] Detaching Volume (%s) from Instance (%s)", vId, iId)
		_, err = stateConf.WaitForState()
		if err != nil {
			return fmt.Errorf(
				"Error waiting for Volume (%s) to detach from Instance (%s): %s",
				vId, iId, err)
		}

		return err
	}
}

func testAccCheckVolumeAttachmentDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).ec2conn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_volume_attachment" {
			continue
		}

		request := &ec2.DescribeVolumesInput{
			VolumeIds: []*string{aws.String(rs.Primary.Attributes["volume_id"])},
			Filters: []*ec2.Filter{
				{
					Name:   aws.String("attachment.device"),
					Values: []*string{aws.String(rs.Primary.Attributes["device_name"])},
				},
				{
					Name:   aws.String("attachment.instance-id"),
					Values: []*string{aws.String(rs.Primary.Attributes["instance_id"])},
				},
			},
		}

		_, err := conn.DescribeVolumes(request)
		if err != nil {
			if isAWSErr(err, "InvalidVolume.NotFound", "") {
				return nil
			}
			return fmt.Errorf("error describing volumes (%s): %s", rs.Primary.ID, err)
		}
	}
	return nil
}

const testAccVolumeAttachmentConfigInstanceOnly = `
data "aws_availability_zones" "available" {
  state = "available"

  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

resource "aws_instance" "test" {
  ami               = "ami-21f78e11"
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  instance_type     = "t1.micro"

  tags = {
    Name = "tf-acc-test-volume-attachment"
  }
}
`

const testAccVolumeAttachmentConfig = testAccVolumeAttachmentConfigInstanceOnly + `
resource "aws_ebs_volume" "test" {
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  size              = 1

  tags = {
    Name = "tf-acc-test-volume-attachment"
  }
}

resource "aws_volume_attachment" "test" {
  device_name = "/dev/sdh"
  volume_id   = "${aws_ebs_volume.test.id}"
  instance_id = "${aws_instance.test.id}"
}
`

const testAccVolumeAttachmentConfigSkipDestroy = testAccVolumeAttachmentConfigInstanceOnly + `
resource "aws_ebs_volume" "test" {
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  size              = 1

  tags = {
    Name = "tf-acc-test-volume-attachment"
  }
}

data "aws_ebs_volume" "test" {
  filter {
    name = "size"
    values = ["${aws_ebs_volume.test.size}"]
  }
  filter {
    name = "availability-zone"
    values = ["${aws_ebs_volume.test.availability_zone}"]
  }
  filter {
    name = "tag:Name"
    values = ["tf-acc-test-volume-attachment"]
  }
}

resource "aws_volume_attachment" "test" {
  device_name  = "/dev/sdh"
  volume_id    = "${data.aws_ebs_volume.test.id}"
  instance_id  = "${aws_instance.test.id}"
  skip_destroy = true
}
`

func testAccVolumeAttachmentConfig_update(detach bool) string {
	return testAccVolumeAttachmentConfigInstanceOnly + fmt.Sprintf(`
resource "aws_ebs_volume" "test" {
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  size              = 1

  tags = {
    Name = "tf-acc-test-volume-attachment"
  }
}

resource "aws_volume_attachment" "test" {
  device_name  = "/dev/sdh"
  volume_id    = "${aws_ebs_volume.test.id}"
  instance_id  = "${aws_instance.test.id}"
  force_detach = %t
  skip_destroy = %t
}
`, detach, detach)
}

func testAccAWSVolumeAttachmentImportStateIDFunc(resourceName string) resource.ImportStateIdFunc {
	return func(s *terraform.State) (string, error) {
		rs, ok := s.RootModule().Resources[resourceName]
		if !ok {
			return "", fmt.Errorf("Not found: %s", resourceName)
		}
		return fmt.Sprintf("%s:%s:%s", rs.Primary.Attributes["device_name"], rs.Primary.Attributes["volume_id"], rs.Primary.Attributes["instance_id"]), nil
	}
}
<commit_msg>fix missing test<commit_after>package aws

import (
	"fmt"
	"log"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSVolumeAttachment_basic(t *testing.T) {
	var i ec2.Instance
	var v ec2.Volume
	resourceName := "aws_volume_attachment.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfig,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "device_name", "/dev/sdh"),
					testAccCheckInstanceExists("aws_instance.test", &i),
					testAccCheckVolumeExists("aws_ebs_volume.test", &v),
					testAccCheckVolumeAttachmentExists(resourceName, &i, &v),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSVolumeAttachment_skipDestroy(t *testing.T) {
	var i ec2.Instance
	var v ec2.Volume
	resourceName := "aws_volume_attachment.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfigSkipDestroy,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "device_name", "/dev/sdh"),
					testAccCheckInstanceExists("aws_instance.test", &i),
					testAccCheckVolumeExists("aws_ebs_volume.test", &v),
					testAccCheckVolumeAttachmentExists(resourceName, &i, &v),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
				ImportStateVerifyIgnore: []string{
					"skip_destroy", // attribute only used on resource deletion
				},
			},
		},
	})
}

func TestAccAWSVolumeAttachment_attachStopped(t *testing.T) {
	var i ec2.Instance
	var v ec2.Volume
	resourceName := "aws_volume_attachment.test"

	stopInstance := func() {
		conn := testAccProvider.Meta().(*AWSClient).ec2conn

		_, err := conn.StopInstances(&ec2.StopInstancesInput{
			InstanceIds: []*string{i.InstanceId},
		})
		if err != nil {
			t.Fatalf("error stopping instance (%s): %s", aws.StringValue(i.InstanceId), err)
		}

		stateConf := &resource.StateChangeConf{
			Pending:    []string{ec2.InstanceStateNamePending, ec2.InstanceStateNameRunning, ec2.InstanceStateNameStopping},
			Target:     []string{ec2.InstanceStateNameStopped},
			Refresh:    InstanceStateRefreshFunc(conn, *i.InstanceId, []string{}),
			Timeout:    10 * time.Minute,
			Delay:      10 * time.Second,
			MinTimeout: 3 * time.Second,
		}

		_, err = stateConf.WaitForState()
		if err != nil {
			t.Fatalf("Error waiting for instance(%s) to stop: %s", *i.InstanceId, err)
		}
	}

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfigInstanceOnly,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckInstanceExists("aws_instance.test", &i),
				),
			},
			{
				PreConfig: stopInstance,
				Config:    testAccVolumeAttachmentConfig,
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "device_name", "/dev/sdh"),
					testAccCheckInstanceExists("aws_instance.test", &i),
					testAccCheckVolumeExists("aws_ebs_volume.test", &v),
					testAccCheckVolumeAttachmentExists(resourceName, &i, &v),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSVolumeAttachment_update(t *testing.T) {
	resourceName := "aws_volume_attachment.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfig_update(false),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "force_detach", "false"),
					resource.TestCheckResourceAttr(resourceName, "skip_destroy", "false"),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
				ImportStateVerifyIgnore: []string{
					"force_detach", // attribute only used on resource deletion
					"skip_destroy", // attribute only used on resource deletion
				},
			},
			{
				Config: testAccVolumeAttachmentConfig_update(true),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttr(resourceName, "force_detach", "true"),
					resource.TestCheckResourceAttr(resourceName, "skip_destroy", "true"),
				),
			},
			{
				ResourceName:      "aws_volume_attachment.ebs_att",
				ImportState:       true,
				ImportStateIdFunc: testAccAWSVolumeAttachmentImportStateIDFunc("aws_volume_attachment.ebs_att"),
				ImportStateVerify: true,
				ImportStateVerifyIgnore: []string{
					"force_detach", // attribute only used on resource deletion
					"skip_destroy", // attribute only used on resource deletion
				},
			},
		},
	})
}

func TestAccAWSVolumeAttachment_disappears(t *testing.T) {
	var i ec2.Instance
	var v ec2.Volume
	resourceName := "aws_volume_attachment.test"

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckVolumeAttachmentDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccVolumeAttachmentConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckInstanceExists("aws_instance.test", &i),
					testAccCheckVolumeExists("aws_ebs_volume.test", &v),
					testAccCheckVolumeAttachmentExists(resourceName, &i, &v),
					testAccCheckVolumeAttachmentDisappears(resourceName, &i, &v),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func testAccCheckVolumeAttachmentExists(n string, i *ec2.Instance, v *ec2.Volume) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		for _, b := range i.BlockDeviceMappings {
			if rs.Primary.Attributes["device_name"] == *b.DeviceName {
				if b.Ebs.VolumeId != nil &&
					rs.Primary.Attributes["volume_id"] == *b.Ebs.VolumeId &&
					rs.Primary.Attributes["volume_id"] == *v.VolumeId {
					// pass
					return nil
				}
			}
		}

		return fmt.Errorf("Error finding instance/volume")
	}
}

func testAccCheckVolumeAttachmentDisappears(n string, i *ec2.Instance, v *ec2.Volume) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).ec2conn

		opts := &ec2.DetachVolumeInput{
			Device:     aws.String(rs.Primary.Attributes["device_name"]),
			InstanceId: i.InstanceId,
			VolumeId:   v.VolumeId,
			Force:      aws.Bool(true),
		}

		_, err := conn.DetachVolume(opts)
		if err != nil {
			return err
		}

		vId := aws.StringValue(v.VolumeId)
		iId := aws.StringValue(i.InstanceId)

		stateConf := &resource.StateChangeConf{
			Pending:    []string{ec2.VolumeAttachmentStateDetaching},
			Target:     []string{ec2.VolumeAttachmentStateDetached},
			Refresh:    volumeAttachmentStateRefreshFunc(conn, rs.Primary.Attributes["device_name"], vId, iId),
			Timeout:    5 * time.Minute,
			Delay:      10 * time.Second,
			MinTimeout: 3 * time.Second,
		}

		log.Printf("[DEBUG] Detaching Volume (%s) from Instance (%s)", vId, iId)
		_, err = stateConf.WaitForState()
		if err != nil {
			return fmt.Errorf(
				"Error waiting for Volume (%s) to detach from Instance (%s): %s",
				vId, iId, err)
		}

		return err
	}
}

func testAccCheckVolumeAttachmentDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).ec2conn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_volume_attachment" {
			continue
		}

		request := &ec2.DescribeVolumesInput{
			VolumeIds: []*string{aws.String(rs.Primary.Attributes["volume_id"])},
			Filters: []*ec2.Filter{
				{
					Name:   aws.String("attachment.device"),
					Values: []*string{aws.String(rs.Primary.Attributes["device_name"])},
				},
				{
					Name:   aws.String("attachment.instance-id"),
					Values: []*string{aws.String(rs.Primary.Attributes["instance_id"])},
				},
			},
		}

		_, err := conn.DescribeVolumes(request)
		if err != nil {
			if isAWSErr(err, "InvalidVolume.NotFound", "") {
				return nil
			}
			return fmt.Errorf("error describing volumes (%s): %s", rs.Primary.ID, err)
		}
	}
	return nil
}

const testAccVolumeAttachmentConfigInstanceOnly = `
data "aws_availability_zones" "available" {
  state = "available"

  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

resource "aws_instance" "test" {
  ami               = "ami-21f78e11"
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  instance_type     = "t1.micro"

  tags = {
    Name = "tf-acc-test-volume-attachment"
  }
}
`

const testAccVolumeAttachmentConfig = testAccVolumeAttachmentConfigInstanceOnly + `
resource "aws_ebs_volume" "test" {
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  size              = 1

  tags = {
    Name = "tf-acc-test-volume-attachment"
  }
}

resource "aws_volume_attachment" "test" {
  device_name = "/dev/sdh"
  volume_id   = "${aws_ebs_volume.test.id}"
  instance_id = "${aws_instance.test.id}"
}
`

const testAccVolumeAttachmentConfigSkipDestroy = testAccVolumeAttachmentConfigInstanceOnly + `
resource "aws_ebs_volume" "test" {
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  size              = 1

  tags = {
    Name = "tf-acc-test-volume-attachment"
  }
}

data "aws_ebs_volume" "test" {
  filter {
    name = "size"
    values = ["${aws_ebs_volume.test.size}"]
  }
  filter {
    name = "availability-zone"
    values = ["${aws_ebs_volume.test.availability_zone}"]
  }
  filter {
    name = "tag:Name"
    values = ["tf-acc-test-volume-attachment"]
  }
}

resource "aws_volume_attachment" "test" {
  device_name  = "/dev/sdh"
  volume_id    = "${data.aws_ebs_volume.test.id}"
  instance_id  = "${aws_instance.test.id}"
  skip_destroy = true
}
`

func testAccVolumeAttachmentConfig_update(detach bool) string {
	return testAccVolumeAttachmentConfigInstanceOnly + fmt.Sprintf(`
resource "aws_ebs_volume" "test" {
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  size              = 1

  tags = {
    Name = "tf-acc-test-volume-attachment"
  }
}

resource "aws_volume_attachment" "test" {
  device_name  = "/dev/sdh"
  volume_id    = "${aws_ebs_volume.test.id}"
  instance_id  = "${aws_instance.test.id}"
  force_detach = %t
  skip_destroy = %t
}
`, detach, detach)
}

func testAccAWSVolumeAttachmentImportStateIDFunc(resourceName string) resource.ImportStateIdFunc {
	return func(s *terraform.State) (string, error) {
		rs, ok := s.RootModule().Resources[resourceName]
		if !ok {
			return "", fmt.Errorf("Not found: %s", resourceName)
		}
		return fmt.Sprintf("%s:%s:%s", rs.Primary.Attributes["device_name"], rs.Primary.Attributes["volume_id"], rs.Primary.Attributes["instance_id"]), nil
	}
}
<|endoftext|>


<commit_before>package flow

import (
	"fmt"
	"testing"
)

func TestMapSingleParameter(t *testing.T) {

	New().Slice(
		[]int{1, 2, 3, 4, 5},
	).Map(func(t int) (int, int) {
		return t, t * 7
	}).Map(func(x, y int) {
		fmt.Println("x=", x, "7*x=", y)
	}).Run()

	t.Logf("single parameter mapping runs well")

}

func TestGroupByKeyMap(t *testing.T) {

	t.Logf("group by result mapping testing start...")
	New().Slice(
		[]int{1, 1, 1, 1, 1, 2, 2, 2, 2,
			3, 3, 3, 4, 4, 5,
			100, 234, 43, 100, 43, 43, 43},
	).Map(func(t int) (int, int) {
		return t, t * 2
	}).Map(func(key, value int) (int, int) {
		return key, value * 3
	}).GroupByKey().Map(func(key int, values []int) {
		fmt.Printf("key: %d values: %v\n", key, values)
	}).Run()

	t.Logf("group by result mapping runs well")

}

func TestCoGroupMap(t *testing.T) {

	t.Logf("cogroup testing start...")
	f := New()
	left := f.Slice(
		[]int{1, 1, 1, 1, 1, 2, 2, 2, 2,
			3, 3, 3, 4, 4, 5,
			100, 234, 43, 100, 43, 43, 43},
	).Map(func(t int) (int, int) {
		return t, t * 2
	})

	right := f.Slice(
		[]int{1, 1, 2, 2,
			3, 3, 3, 4, 4, 5,
			100, 234, 43, 99, 44, 44, 50},
	).Map(func(t int) (int, int) {
		return t, t * 5
	})

	left.CoGroup(right).Map(func(key int, lefts, rights []int) {
		fmt.Printf("key: %d left: %v, right: %v\n", key, lefts, rights)
	}).Run()

	t.Logf("cogroup runs well")

}
<commit_msg>adjust print<commit_after>package flow

import (
	"fmt"
	"testing"
)

func TestMapSingleParameter(t *testing.T) {

	New().Slice(
		[]int{1, 2, 3, 4, 5},
	).Map(func(t int) (int, int) {
		return t, t * 7
	}).Map(func(x, y int) {
		fmt.Println("x=", x, "7*x=", y)
	}).Run()

	t.Logf("single parameter mapping runs well")

}

func TestGroupByKeyMap(t *testing.T) {

	fmt.Println("group by result mapping testing start...")
	New().Slice(
		[]int{1, 1, 1, 1, 1, 2, 2, 2, 2,
			3, 3, 3, 4, 4, 5,
			100, 234, 43, 100, 43, 43, 43},
	).Map(func(t int) (int, int) {
		return t, t * 2
	}).Map(func(key, value int) (int, int) {
		return key, value * 3
	}).GroupByKey().Map(func(key int, values []int) {
		fmt.Printf("key: %d values: %v\n", key, values)
	}).Run()

	fmt.Println("group by result mapping runs well")

}

func TestCoGroupMap(t *testing.T) {

	fmt.Println("cogroup testing start...")
	f := New()
	left := f.Slice(
		[]int{1, 1, 1, 1, 1, 2, 2, 2, 2,
			3, 3, 3, 4, 4, 5,
			100, 234, 43, 100, 43, 43, 43},
	).Map(func(t int) (int, int) {
		return t, t * 2
	})

	right := f.Slice(
		[]int{1, 1, 2, 2,
			3, 3, 3, 4, 4, 5,
			100, 234, 43, 99, 44, 44, 50},
	).Map(func(t int) (int, int) {
		return t, t * 5
	})

	left.CoGroup(right).Map(func(key int, lefts, rights []int) {
		fmt.Printf("key: %d left: %v, right: %v\n", key, lefts, rights)
	}).Run()

	fmt.Println("cogroup runs well")

}
<|endoftext|>


<commit_before>package proto

type Details struct {
	CreatedAt string `json:"createdAt,omitempty"`
	CreatedBy string `json:"createdBy,omitempty"`
	Server    Server `json:"server,omitempty"`
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Proto update: add []CheckConfig to Details<commit_after>package proto

type Details struct {
	CreatedAt    string         `json:"createdAt,omitempty"`
	CreatedBy    string         `json:"createdBy,omitempty"`
	Server       Server         `json:"server,omitempty"`
	CheckConfigs *[]CheckConfig `json:"checkConfigs,omitempty"`
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>package db

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"code.cloudfoundry.org/lager"

	sq "github.com/Masterminds/squirrel"
	"github.com/concourse/concourse/atc/db/lock"
)

//counterfeiter:generate . BuildForAPI

// BuildForAPI is a smaller interface of db.Build that should only be used within
// API packages.
type BuildForAPI interface {
	PipelineRef

	ID() int
	Name() string

	TeamID() int
	TeamName() string

	JobID() int
	JobName() string

	Job() (Job, bool, error)

	// AllAssociatedTeamNames is only meaningful for check build. For a global
	// resource's check build, it may associate to resources across multiple
	// teams.
	AllAssociatedTeamNames() []string

	ResourceID() int
	ResourceName() string

	LagerData() lager.Data
	Schema() string
	PublicPlan() *json.RawMessage
	HasPlan() bool

	Comment() string
	StartTime() time.Time
	EndTime() time.Time
	ReapTime() time.Time
	Status() BuildStatus
	RerunOf() int
	RerunOfName() string
	RerunNumber() int
	CreatedBy() *string

	IsDrained() bool
	IsRunning() bool

	Artifacts() ([]WorkerArtifact, error)
	Events(uint) (EventSource, error)
	Resources() ([]BuildInput, []BuildOutput, error)
	Preparation() (BuildPreparation, bool, error)

	MarkAsAborted() error
	SetComment(string) error
}

//counterfeiter:generate . BuildFactory
type BuildFactory interface {
	BuildForAPI(int) (BuildForAPI, bool, error)
	VisibleBuilds([]string, Page) ([]BuildForAPI, Pagination, error)
	AllBuilds(Page) ([]BuildForAPI, Pagination, error)
	PublicBuilds(Page) ([]BuildForAPI, Pagination, error)

	Build(int) (Build, bool, error)
	GetAllStartedBuilds() ([]Build, error)
	GetDrainableBuilds() ([]Build, error)

	// TODO: move to BuildLifecycle, new interface (see WorkerLifecycle)
	MarkNonInterceptibleBuilds() error
}

type buildFactory struct {
	conn              Conn
	lockFactory       lock.LockFactory
	oneOffGracePeriod time.Duration
	failedGracePeriod time.Duration
}

func NewBuildFactory(conn Conn, lockFactory lock.LockFactory, oneOffGracePeriod time.Duration, failedGracePeriod time.Duration) BuildFactory {
	return &buildFactory{
		conn:              conn,
		lockFactory:       lockFactory,
		oneOffGracePeriod: oneOffGracePeriod,
		failedGracePeriod: failedGracePeriod,
	}
}

func (f *buildFactory) BuildForAPI(buildID int) (BuildForAPI, bool, error) {
	build := newEmptyBuild(f.conn, f.lockFactory)
	row := buildsQuery.
		Where(sq.Eq{"b.id": buildID}).
		RunWith(f.conn).
		QueryRow()

	err := scanBuild(build, row, f.conn.EncryptionStrategy())
	if err != nil {
		if err == sql.ErrNoRows {
			// If it cannot find the build from table "builds", then try to see
			// if the build is an in-memory build. As in-memory only runs against
			// resources, thus we only need to try search for resources.
			resource, found, err := f.findResourceOfInMemoryCheckBuild(buildID)
			if err != nil {
				return nil, false, err
			}

			if !found {
				return nil, false, nil
			}

			build, err := newExistingInMemoryCheckBuildForApi(f.conn, buildID, resource)
			if err != nil {
				return nil, false, err
			}

			return build, true, nil
		}

		return nil, false, err
	}

	return build, true, nil
}

func (f *buildFactory) Build(buildID int) (Build, bool, error) {
	build := newEmptyBuild(f.conn, f.lockFactory)
	row := buildsQuery.
		Where(sq.Eq{"b.id": buildID}).
		RunWith(f.conn).
		QueryRow()

	err := scanBuild(build, row, f.conn.EncryptionStrategy())
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, false, nil
		}

		return nil, false, err
	}

	return build, true, nil
}

func (f *buildFactory) VisibleBuilds(teamNames []string, page Page) ([]BuildForAPI, Pagination, error) {
	newBuildsQuery := buildsQuery.
		Where(sq.Or{
			sq.Eq{"p.public": true},
			sq.Eq{"t.name": teamNames},
		})

	if page.UseDate {
		return getBuildsWithDates(newBuildsQuery, minMaxIdQuery, page, f.conn,
			f.lockFactory)
	}
	return getBuildsWithPagination(newBuildsQuery, minMaxIdQuery, page, f.conn,
		f.lockFactory)
}

func (f *buildFactory) AllBuilds(page Page) ([]BuildForAPI, Pagination, error) {
	if page.UseDate {
		return getBuildsWithDates(buildsQuery, minMaxIdQuery, page, f.conn,
			f.lockFactory)
	}
	return getBuildsWithPagination(buildsQuery, minMaxIdQuery,
		page, f.conn, f.lockFactory)
}

func (f *buildFactory) PublicBuilds(page Page) ([]BuildForAPI, Pagination, error) {
	return getBuildsWithPagination(
		buildsQuery.Where(sq.Eq{"p.public": true}), minMaxIdQuery,
		page, f.conn, f.lockFactory)
}

func (f *buildFactory) MarkNonInterceptibleBuilds() error {
	_, err := psql.Update("builds b").
		Set("interceptible", false).
		Where(sq.Eq{
			"completed":     true,
			"interceptible": true,
		}).
		Where(sq.Or{
			sq.NotEq{"job_id": nil},
			sq.Expr(fmt.Sprintf("now() - end_time > '%d seconds'::interval", int(f.oneOffGracePeriod.Seconds()))),
		}).
		Where(f.constructBuildFilter()).
		RunWith(f.conn).
		Exec()
	return err
}

func (f *buildFactory) constructBuildFilter() sq.Or {
	buildFilter := sq.Or{
		sq.Expr("NOT EXISTS (SELECT 1 FROM jobs j WHERE j.latest_completed_build_id = b.id)"),
		sq.Eq{"status": string(BuildStatusSucceeded)},
	}
	if f.failedGracePeriod > 0 { // if zero, grace period is disabled
		buildFilter = append(buildFilter,
			sq.Expr(fmt.Sprintf("now() - end_time > '%d seconds'::interval", int(f.failedGracePeriod.Seconds()))))
	}
	return buildFilter
}

func (f *buildFactory) GetDrainableBuilds() ([]Build, error) {
	query := buildsQuery.Where(sq.Eq{
		"b.completed": true,
		"b.drained":   false,
	})

	return getBuilds(query, f.conn, f.lockFactory)
}

func (f *buildFactory) GetAllStartedBuilds() ([]Build, error) {
	query := buildsQuery.Where(sq.Eq{
		"b.status": BuildStatusStarted,
	})

	return getBuilds(query, f.conn, f.lockFactory)
}

func (f *buildFactory) findResourceOfInMemoryCheckBuild(buildId int) (Resource, bool, error) {
	resource := newEmptyResource(f.conn, f.lockFactory)
	row := resourcesQuery.
		Where(sq.Or{
			sq.Eq{"r.in_memory_build_id": buildId},
			sq.Eq{"rs.last_check_build_id": buildId},
		}).
		Limit(1).
		RunWith(f.conn).
		QueryRow()
	err := scanResource(resource, row)
	if err == sql.ErrNoRows {
		return nil, false, nil
	}
	if err != nil {
		return nil, false, err
	}
	return resource, true, nil
}

func getBuilds(buildsQuery sq.SelectBuilder, conn Conn, lockFactory lock.LockFactory) ([]Build, error) {
	rows, err := buildsQuery.RunWith(conn).Query()
	if err != nil {
		return nil, err
	}

	defer Close(rows)

	bs := []Build{}

	for rows.Next() {
		b := newEmptyBuild(conn, lockFactory)
		err := scanBuild(b, rows, conn.EncryptionStrategy())
		if err != nil {
			return nil, err
		}

		bs = append(bs, b)
	}

	return bs, nil
}

func getBuildsWithDates(buildsQuery, minMaxIdQuery sq.SelectBuilder, page Page, conn Conn, lockFactory lock.LockFactory) ([]BuildForAPI, Pagination, error) {
	var newPage = Page{Limit: page.Limit}

	tx, err := conn.Begin()
	if err != nil {
		return nil, Pagination{}, err
	}

	defer Rollback(tx)

	if page.From != nil {
		fromRow, err := buildsQuery.
			Where(sq.Expr("b.start_time >= to_timestamp(" + strconv.Itoa(*page.From) + ")")).
			OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC").
			Limit(1).
			RunWith(tx).
			Query()

		if err != nil {
			// The user has no builds since that given time
			if err == sql.ErrNoRows {
				return []BuildForAPI{}, Pagination{}, nil
			}

			return nil, Pagination{}, err
		}

		defer fromRow.Close()

		found := false
		for fromRow.Next() {
			found = true
			build := newEmptyBuild(conn, lockFactory)
			err = scanBuild(build, fromRow, conn.EncryptionStrategy())
			if err != nil {
				return nil, Pagination{}, err
			}

			newPage.From = NewIntPtr(build.ID())
		}
		if !found {
			return []BuildForAPI{}, Pagination{}, nil
		}
	}

	if page.To != nil {
		untilRow, err := buildsQuery.
			Where(sq.Expr("b.start_time <= to_timestamp(" + strconv.Itoa(*page.To) + ")")).
			OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC").
			Limit(1).
			RunWith(tx).
			Query()
		if err != nil {
			// The user has no builds since that given time
			if err == sql.ErrNoRows {
				return []BuildForAPI{}, Pagination{}, nil
			}
		}

		defer untilRow.Close()

		found := false
		for untilRow.Next() {
			found = true
			build := newEmptyBuild(conn, lockFactory)
			err = scanBuild(build, untilRow, conn.EncryptionStrategy())
			if err != nil {
				return nil, Pagination{}, err
			}

			newPage.To = NewIntPtr(build.ID())
		}
		if !found {
			return []BuildForAPI{}, Pagination{}, nil
		}
	}

	err = tx.Commit()
	if err != nil {
		return nil, Pagination{}, err
	}

	return getBuildsWithPagination(buildsQuery, minMaxIdQuery, newPage, conn, lockFactory)
}

func getBuildsWithPagination(buildsQuery, minMaxIdQuery sq.SelectBuilder, page Page, conn Conn, lockFactory lock.LockFactory) ([]BuildForAPI, Pagination, error) {
	var (
		rows    *sql.Rows
		err     error
		reverse bool
	)

	origBuildsQuery := buildsQuery

	tx, err := conn.Begin()
	if err != nil {
		return nil, Pagination{}, err
	}

	defer Rollback(tx)

	buildsQuery = buildsQuery.Limit(uint64(page.Limit))

	if page.From == nil && page.To == nil { // none
		buildsQuery = buildsQuery.
			OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC")
	} else if page.From != nil && page.To == nil { // only from
		buildsQuery = buildsQuery.
			Where(sq.GtOrEq{"b.id": uint64(*page.From)}).
			OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC")
		reverse = true
	} else if page.From == nil && page.To != nil { // only to
		buildsQuery = buildsQuery.
			Where(sq.LtOrEq{"b.id": uint64(*page.To)}).
			OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC")
	} else if page.From != nil && page.To != nil { // both
		if *page.From > *page.To {
			return nil, Pagination{}, fmt.Errorf("invalid range boundaries")
		}

		buildsQuery = buildsQuery.Where(
			sq.And{
				sq.GtOrEq{"b.id": uint64(*page.From)},
				sq.LtOrEq{"b.id": uint64(*page.To)},
			}).
			OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC")
	}

	rows, err = buildsQuery.RunWith(tx).Query()
	if err != nil {
		return nil, Pagination{}, err
	}

	defer Close(rows)

	builds := make([]BuildForAPI, 0)
	for rows.Next() {
		build := newEmptyBuild(conn, lockFactory)
		err = scanBuild(build, rows, conn.EncryptionStrategy())
		if err != nil {
			return nil, Pagination{}, err
		}

		builds = append(builds, build)
	}

	if reverse {
		for i, j := 0, len(builds)-1; i < j; i, j = i+1, j-1 {
			builds[i], builds[j] = builds[j], builds[i]
		}
	}

	if len(builds) == 0 {
		return builds, Pagination{}, nil
	}

	newestBuild := builds[0]
	oldestBuild := builds[len(builds)-1]

	var pagination Pagination

	row := origBuildsQuery.
		Where(sq.Lt{"b.id": oldestBuild.ID()}).
		OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC").
		Limit(1).
		RunWith(tx).
		QueryRow()

	build := newEmptyBuild(conn, lockFactory)
	err = scanBuild(build, row, conn.EncryptionStrategy())
	if err != nil && err != sql.ErrNoRows {
		return builds, Pagination{}, err
	} else if err == nil {
		pagination.Older = &Page{
			To:    &build.id,
			Limit: page.Limit,
		}
	}

	row = origBuildsQuery.
		Where(sq.Gt{"b.id": newestBuild.ID()}).
		OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC").
		Limit(1).
		RunWith(tx).
		QueryRow()

	build = newEmptyBuild(conn, lockFactory)
	err = scanBuild(build, row, conn.EncryptionStrategy())
	if err != nil && err != sql.ErrNoRows {
		return builds, Pagination{}, err
	} else if err == nil {
		pagination.Newer = &Page{
			From:  &build.id,
			Limit: page.Limit,
		}
	}

	err = tx.Commit()
	if err != nil {
		return nil, Pagination{}, err
	}

	return builds, pagination, nil
}
<commit_msg>exclude check builds for syslog drainer<commit_after>package db

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"code.cloudfoundry.org/lager"

	sq "github.com/Masterminds/squirrel"
	"github.com/concourse/concourse/atc/db/lock"
)

//counterfeiter:generate . BuildForAPI

// BuildForAPI is a smaller interface of db.Build that should only be used within
// API packages.
type BuildForAPI interface {
	PipelineRef

	ID() int
	Name() string

	TeamID() int
	TeamName() string

	JobID() int
	JobName() string

	Job() (Job, bool, error)

	// AllAssociatedTeamNames is only meaningful for check build. For a global
	// resource's check build, it may associate to resources across multiple
	// teams.
	AllAssociatedTeamNames() []string

	ResourceID() int
	ResourceName() string

	LagerData() lager.Data
	Schema() string
	PublicPlan() *json.RawMessage
	HasPlan() bool

	Comment() string
	StartTime() time.Time
	EndTime() time.Time
	ReapTime() time.Time
	Status() BuildStatus
	RerunOf() int
	RerunOfName() string
	RerunNumber() int
	CreatedBy() *string

	IsDrained() bool
	IsRunning() bool

	Artifacts() ([]WorkerArtifact, error)
	Events(uint) (EventSource, error)
	Resources() ([]BuildInput, []BuildOutput, error)
	Preparation() (BuildPreparation, bool, error)

	MarkAsAborted() error
	SetComment(string) error
}

//counterfeiter:generate . BuildFactory
type BuildFactory interface {
	BuildForAPI(int) (BuildForAPI, bool, error)
	VisibleBuilds([]string, Page) ([]BuildForAPI, Pagination, error)
	AllBuilds(Page) ([]BuildForAPI, Pagination, error)
	PublicBuilds(Page) ([]BuildForAPI, Pagination, error)

	Build(int) (Build, bool, error)
	GetAllStartedBuilds() ([]Build, error)
	GetDrainableBuilds() ([]Build, error)

	// TODO: move to BuildLifecycle, new interface (see WorkerLifecycle)
	MarkNonInterceptibleBuilds() error
}

type buildFactory struct {
	conn              Conn
	lockFactory       lock.LockFactory
	oneOffGracePeriod time.Duration
	failedGracePeriod time.Duration
}

func NewBuildFactory(conn Conn, lockFactory lock.LockFactory, oneOffGracePeriod time.Duration, failedGracePeriod time.Duration) BuildFactory {
	return &buildFactory{
		conn:              conn,
		lockFactory:       lockFactory,
		oneOffGracePeriod: oneOffGracePeriod,
		failedGracePeriod: failedGracePeriod,
	}
}

func (f *buildFactory) BuildForAPI(buildID int) (BuildForAPI, bool, error) {
	build := newEmptyBuild(f.conn, f.lockFactory)
	row := buildsQuery.
		Where(sq.Eq{"b.id": buildID}).
		RunWith(f.conn).
		QueryRow()

	err := scanBuild(build, row, f.conn.EncryptionStrategy())
	if err != nil {
		if err == sql.ErrNoRows {
			// If it cannot find the build from table "builds", then try to see
			// if the build is an in-memory build. As in-memory only runs against
			// resources, thus we only need to try search for resources.
			resource, found, err := f.findResourceOfInMemoryCheckBuild(buildID)
			if err != nil {
				return nil, false, err
			}

			if !found {
				return nil, false, nil
			}

			build, err := newExistingInMemoryCheckBuildForApi(f.conn, buildID, resource)
			if err != nil {
				return nil, false, err
			}

			return build, true, nil
		}

		return nil, false, err
	}

	return build, true, nil
}

func (f *buildFactory) Build(buildID int) (Build, bool, error) {
	build := newEmptyBuild(f.conn, f.lockFactory)
	row := buildsQuery.
		Where(sq.Eq{"b.id": buildID}).
		RunWith(f.conn).
		QueryRow()

	err := scanBuild(build, row, f.conn.EncryptionStrategy())
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, false, nil
		}

		return nil, false, err
	}

	return build, true, nil
}

func (f *buildFactory) VisibleBuilds(teamNames []string, page Page) ([]BuildForAPI, Pagination, error) {
	newBuildsQuery := buildsQuery.
		Where(sq.Or{
			sq.Eq{"p.public": true},
			sq.Eq{"t.name": teamNames},
		})

	if page.UseDate {
		return getBuildsWithDates(newBuildsQuery, minMaxIdQuery, page, f.conn,
			f.lockFactory)
	}
	return getBuildsWithPagination(newBuildsQuery, minMaxIdQuery, page, f.conn,
		f.lockFactory)
}

func (f *buildFactory) AllBuilds(page Page) ([]BuildForAPI, Pagination, error) {
	if page.UseDate {
		return getBuildsWithDates(buildsQuery, minMaxIdQuery, page, f.conn,
			f.lockFactory)
	}
	return getBuildsWithPagination(buildsQuery, minMaxIdQuery,
		page, f.conn, f.lockFactory)
}

func (f *buildFactory) PublicBuilds(page Page) ([]BuildForAPI, Pagination, error) {
	return getBuildsWithPagination(
		buildsQuery.Where(sq.Eq{"p.public": true}), minMaxIdQuery,
		page, f.conn, f.lockFactory)
}

func (f *buildFactory) MarkNonInterceptibleBuilds() error {
	_, err := psql.Update("builds b").
		Set("interceptible", false).
		Where(sq.Eq{
			"completed":     true,
			"interceptible": true,
		}).
		Where(sq.Or{
			sq.NotEq{"job_id": nil},
			sq.Expr(fmt.Sprintf("now() - end_time > '%d seconds'::interval", int(f.oneOffGracePeriod.Seconds()))),
		}).
		Where(f.constructBuildFilter()).
		RunWith(f.conn).
		Exec()
	return err
}

func (f *buildFactory) constructBuildFilter() sq.Or {
	buildFilter := sq.Or{
		sq.Expr("NOT EXISTS (SELECT 1 FROM jobs j WHERE j.latest_completed_build_id = b.id)"),
		sq.Eq{"status": string(BuildStatusSucceeded)},
	}
	if f.failedGracePeriod > 0 { // if zero, grace period is disabled
		buildFilter = append(buildFilter,
			sq.Expr(fmt.Sprintf("now() - end_time > '%d seconds'::interval", int(f.failedGracePeriod.Seconds()))))
	}
	return buildFilter
}

func (f *buildFactory) GetDrainableBuilds() ([]Build, error) {
	query := buildsQuery.Where(
		sq.Eq{
			"b.completed":        true,
			"b.drained":          false,
			"b.resource_id":      nil,
			"b.resource_type_id": nil,
		})

	return getBuilds(query, f.conn, f.lockFactory)
}

func (f *buildFactory) GetAllStartedBuilds() ([]Build, error) {
	query := buildsQuery.Where(sq.Eq{
		"b.status": BuildStatusStarted,
	})

	return getBuilds(query, f.conn, f.lockFactory)
}

func (f *buildFactory) findResourceOfInMemoryCheckBuild(buildId int) (Resource, bool, error) {
	resource := newEmptyResource(f.conn, f.lockFactory)
	row := resourcesQuery.
		Where(sq.Or{
			sq.Eq{"r.in_memory_build_id": buildId},
			sq.Eq{"rs.last_check_build_id": buildId},
		}).
		Limit(1).
		RunWith(f.conn).
		QueryRow()
	err := scanResource(resource, row)
	if err == sql.ErrNoRows {
		return nil, false, nil
	}
	if err != nil {
		return nil, false, err
	}
	return resource, true, nil
}

func getBuilds(buildsQuery sq.SelectBuilder, conn Conn, lockFactory lock.LockFactory) ([]Build, error) {
	rows, err := buildsQuery.RunWith(conn).Query()
	if err != nil {
		return nil, err
	}

	defer Close(rows)

	bs := []Build{}

	for rows.Next() {
		b := newEmptyBuild(conn, lockFactory)
		err := scanBuild(b, rows, conn.EncryptionStrategy())
		if err != nil {
			return nil, err
		}

		bs = append(bs, b)
	}

	return bs, nil
}

func getBuildsWithDates(buildsQuery, minMaxIdQuery sq.SelectBuilder, page Page, conn Conn, lockFactory lock.LockFactory) ([]BuildForAPI, Pagination, error) {
	var newPage = Page{Limit: page.Limit}

	tx, err := conn.Begin()
	if err != nil {
		return nil, Pagination{}, err
	}

	defer Rollback(tx)

	if page.From != nil {
		fromRow, err := buildsQuery.
			Where(sq.Expr("b.start_time >= to_timestamp(" + strconv.Itoa(*page.From) + ")")).
			OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC").
			Limit(1).
			RunWith(tx).
			Query()

		if err != nil {
			// The user has no builds since that given time
			if err == sql.ErrNoRows {
				return []BuildForAPI{}, Pagination{}, nil
			}

			return nil, Pagination{}, err
		}

		defer fromRow.Close()

		found := false
		for fromRow.Next() {
			found = true
			build := newEmptyBuild(conn, lockFactory)
			err = scanBuild(build, fromRow, conn.EncryptionStrategy())
			if err != nil {
				return nil, Pagination{}, err
			}

			newPage.From = NewIntPtr(build.ID())
		}
		if !found {
			return []BuildForAPI{}, Pagination{}, nil
		}
	}

	if page.To != nil {
		untilRow, err := buildsQuery.
			Where(sq.Expr("b.start_time <= to_timestamp(" + strconv.Itoa(*page.To) + ")")).
			OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC").
			Limit(1).
			RunWith(tx).
			Query()
		if err != nil {
			// The user has no builds since that given time
			if err == sql.ErrNoRows {
				return []BuildForAPI{}, Pagination{}, nil
			}
		}

		defer untilRow.Close()

		found := false
		for untilRow.Next() {
			found = true
			build := newEmptyBuild(conn, lockFactory)
			err = scanBuild(build, untilRow, conn.EncryptionStrategy())
			if err != nil {
				return nil, Pagination{}, err
			}

			newPage.To = NewIntPtr(build.ID())
		}
		if !found {
			return []BuildForAPI{}, Pagination{}, nil
		}
	}

	err = tx.Commit()
	if err != nil {
		return nil, Pagination{}, err
	}

	return getBuildsWithPagination(buildsQuery, minMaxIdQuery, newPage, conn, lockFactory)
}

func getBuildsWithPagination(buildsQuery, minMaxIdQuery sq.SelectBuilder, page Page, conn Conn, lockFactory lock.LockFactory) ([]BuildForAPI, Pagination, error) {
	var (
		rows    *sql.Rows
		err     error
		reverse bool
	)

	origBuildsQuery := buildsQuery

	tx, err := conn.Begin()
	if err != nil {
		return nil, Pagination{}, err
	}

	defer Rollback(tx)

	buildsQuery = buildsQuery.Limit(uint64(page.Limit))

	if page.From == nil && page.To == nil { // none
		buildsQuery = buildsQuery.
			OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC")
	} else if page.From != nil && page.To == nil { // only from
		buildsQuery = buildsQuery.
			Where(sq.GtOrEq{"b.id": uint64(*page.From)}).
			OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC")
		reverse = true
	} else if page.From == nil && page.To != nil { // only to
		buildsQuery = buildsQuery.
			Where(sq.LtOrEq{"b.id": uint64(*page.To)}).
			OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC")
	} else if page.From != nil && page.To != nil { // both
		if *page.From > *page.To {
			return nil, Pagination{}, fmt.Errorf("invalid range boundaries")
		}

		buildsQuery = buildsQuery.Where(
			sq.And{
				sq.GtOrEq{"b.id": uint64(*page.From)},
				sq.LtOrEq{"b.id": uint64(*page.To)},
			}).
			OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC")
	}

	rows, err = buildsQuery.RunWith(tx).Query()
	if err != nil {
		return nil, Pagination{}, err
	}

	defer Close(rows)

	builds := make([]BuildForAPI, 0)
	for rows.Next() {
		build := newEmptyBuild(conn, lockFactory)
		err = scanBuild(build, rows, conn.EncryptionStrategy())
		if err != nil {
			return nil, Pagination{}, err
		}

		builds = append(builds, build)
	}

	if reverse {
		for i, j := 0, len(builds)-1; i < j; i, j = i+1, j-1 {
			builds[i], builds[j] = builds[j], builds[i]
		}
	}

	if len(builds) == 0 {
		return builds, Pagination{}, nil
	}

	newestBuild := builds[0]
	oldestBuild := builds[len(builds)-1]

	var pagination Pagination

	row := origBuildsQuery.
		Where(sq.Lt{"b.id": oldestBuild.ID()}).
		OrderBy("COALESCE(b.rerun_of, b.id) DESC, b.id DESC").
		Limit(1).
		RunWith(tx).
		QueryRow()

	build := newEmptyBuild(conn, lockFactory)
	err = scanBuild(build, row, conn.EncryptionStrategy())
	if err != nil && err != sql.ErrNoRows {
		return builds, Pagination{}, err
	} else if err == nil {
		pagination.Older = &Page{
			To:    &build.id,
			Limit: page.Limit,
		}
	}

	row = origBuildsQuery.
		Where(sq.Gt{"b.id": newestBuild.ID()}).
		OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC").
		Limit(1).
		RunWith(tx).
		QueryRow()

	build = newEmptyBuild(conn, lockFactory)
	err = scanBuild(build, row, conn.EncryptionStrategy())
	if err != nil && err != sql.ErrNoRows {
		return builds, Pagination{}, err
	} else if err == nil {
		pagination.Newer = &Page{
			From:  &build.id,
			Limit: page.Limit,
		}
	}

	err = tx.Commit()
	if err != nil {
		return nil, Pagination{}, err
	}

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


<commit_before>// Copyright (C) 2019 Google Inc.
//
// 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 perfetto

import (
	"context"
	"errors"
	"io"
	"net"

	"github.com/google/gapid/core/app"
	"github.com/google/gapid/core/event/task"
	"github.com/google/gapid/core/fault"
	"github.com/google/gapid/gapis/perfetto/client"

	common "protos/perfetto/common"
	config "protos/perfetto/config"
	ipc "protos/perfetto/ipc"
)

const (
	// ErrDone is returned for calls on a trace session that is already stopped.
	ErrDone = fault.Const("Trace already stopped")
	// ErrTracingToFile is returned for read calls on a trace session that is writting to a file on the device.
	ErrTracingToFile = fault.Const("Cannot read buffers, when tracing to a file")

	consumerService = "ConsumerPort"
	queryMethod     = "QueryServiceState"
	traceMethod     = "EnableTracing"
	startMethod     = "StartTracing"
	stopMethod      = "DisableTracing"
	readMethod      = "ReadBuffers"
)

// Client is a client ("consumer") of a Perfetto service.
type Client struct {
	conn    *client.Connection
	methods map[string]*client.Method
}

// NewClient returns a new client using the provided socket connection. The
// client takes ownership of the connection and invokes the provided cleanup
// on shutdown.
func NewClient(ctx context.Context, conn net.Conn, cleanup app.Cleanup) (*Client, error) {
	c, err := client.Connect(ctx, conn, cleanup)
	if err != nil {
		conn.Close()
		cleanup.Invoke(ctx)
		return nil, err
	}

	bind := client.NewBindSync(ctx)
	if err := c.Bind(ctx, consumerService, bind.Handler); err != nil {
		c.Close(ctx)
		return nil, err
	}
	methods, err := bind.Wait(ctx)
	if err != nil {
		c.Close(ctx)
		return nil, err
	}

	return &Client{
		conn:    c,
		methods: methods,
	}, nil
}

// Query queries the Perfetto service for producer and data source info and
// invokes the given callback on each received result. This is a streaming,
// synchronous RPC and the callback may be invoked multiple times.
func (c *Client) Query(ctx context.Context, cb func(*common.TracingServiceState) error) error {
	m, ok := c.methods[queryMethod]
	if !ok {
		return errors.New("Remote service doesn't have a query method")
	}

	query := client.NewQuerySync(ctx, cb)
	if err := c.conn.Invoke(ctx, m, &ipc.QueryServiceStateRequest{}, query.Handler); err != nil {
		return err
	}
	return query.Wait(ctx)
}

// TraceSession is the interface to a currently running Perfetto trace.
type TraceSession struct {
	conn  *client.Connection
	wait  task.Signal
	done  task.Task
	start *client.Method
	read  *client.Method
	stop  *client.Method
	out   *client.PacketWriter
	err   error
}

// Trace initiates a new Perfetto trace session using the given config. The
// trace buffers will be serialized to the given writer. This is an asynchronous
// RPC that can be controlled/waited on using the returned trace session.
func (c *Client) Trace(ctx context.Context, cfg *config.TraceConfig, out io.Writer) (*TraceSession, error) {
	trace, okTrace := c.methods[traceMethod]
	start, _ := c.methods[startMethod] // ignore missing start
	stop, okStop := c.methods[stopMethod]
	read, okRead := c.methods[readMethod]
	if !okTrace || !okStop || !okRead {
		return nil, errors.New("Remote service doesn't have the trace methods")
	}

	if !cfg.GetDeferredStart() {
		start = nil
	}
	if cfg.GetWriteIntoFile() {
		read = nil
	}

	wait, done := task.NewSignal()
	s := &TraceSession{
		conn:  c.conn,
		wait:  wait,
		done:  done,
		start: start,
		read:  read,
		stop:  stop,
		out:   client.NewPacketWriter(out),
	}

	h := client.NewTraceHandler(ctx, func(r *ipc.EnableTracingResponse, err error) {
		if !s.onResult(ctx, err) {
			if s.read == nil {
				s.done(ctx)
			} else {
				s.readBuffers(ctx, func(err error) {
					if !s.onResult(ctx, err) {
						s.done(ctx)
					}
				})
			}
		}
	})

	if err := c.conn.Invoke(ctx, trace, &ipc.EnableTracingRequest{TraceConfig: cfg}, h); err != nil {
		return nil, err
	}

	return s, nil
}

// Start starts the currently deferred trace of this session. Does nothing, if
// the Perfetto service doesn't support deferred tracing or the trace was not
// started in deferred mode.
func (s *TraceSession) Start(ctx context.Context) {
	if s.start == nil || s.wait.Fired() {
		// Ignore any starts if the trace was not defferred or after we've
		// already marked this session as done
		return
	}

	h := client.NewIgnoreHandler(ctx, func(err error) {
		s.onResult(ctx, err)
	})
	s.onResult(ctx, s.conn.Invoke(ctx, s.start, &ipc.StartTracingRequest{}, h))
}

// Read requests the service to transfer the buffered data and writes it into
// the output writer. This is a synchronous call that blocks until the service
// is done sending data and it has been written.
func (s *TraceSession) Read(ctx context.Context) error {
	if s.read == nil {
		return ErrTracingToFile
	} else if s.wait.Fired() {
		// Ignore any reads after we've already marked this session as done.
		return ErrDone
	}

	fail := make(chan error, 1)
	s.readBuffers(ctx, func(err error) {
		fail <- err
	})

	select {
	case err := <-fail:
		return err
	case <-task.ShouldStop(ctx):
		return task.StopReason(ctx)
	}
}

// Stop stops the currently running trace of this session.
func (s *TraceSession) Stop(ctx context.Context) {
	if s.wait.Fired() {
		// Ignore any stops after we've already marked this session as done.
		return
	}

	h := client.NewIgnoreHandler(ctx, func(err error) {
		s.onResult(ctx, err)
	})
	s.onResult(ctx, s.conn.Invoke(ctx, s.stop, &ipc.DisableTracingRequest{}, h))
}

// Wait waits for this trace session to finish and returns any error encountered
// during the trace.
func (s *TraceSession) Wait(ctx context.Context) error {
	if !s.wait.Wait(ctx) {
		return task.StopReason(ctx)
	}
	return s.err
}

func (s *TraceSession) onResult(ctx context.Context, err error) bool {
	if err != nil {
		if s.err == nil {
			s.err = err
		}
		s.done(ctx)
		return true
	}
	return false
}

func (s *TraceSession) readBuffers(ctx context.Context, done func(error)) {
	h := client.NewReadHandler(ctx, func(r *ipc.ReadBuffersResponse, more bool, err error) {
		if err != nil {
			done(err)
			return
		}

		if err := s.out.Write(r.Slices); err != nil {
			done(err)
			return
		}

		if !more {
			done(nil)
		}
	})
	s.onResult(ctx, s.conn.Invoke(ctx, s.read, &ipc.ReadBuffersRequest{}, h))
}

// Close closes the underlying connection to the Perfetto service of this client.
func (c *Client) Close(ctx context.Context) {
	c.conn.Close(ctx)
	c.conn = nil
}
<commit_msg>Don't ignore errors in the EnableTracingResponse.<commit_after>// Copyright (C) 2019 Google Inc.
//
// 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 perfetto

import (
	"context"
	"errors"
	"io"
	"net"

	"github.com/google/gapid/core/app"
	"github.com/google/gapid/core/event/task"
	"github.com/google/gapid/core/fault"
	"github.com/google/gapid/gapis/perfetto/client"

	common "protos/perfetto/common"
	config "protos/perfetto/config"
	ipc "protos/perfetto/ipc"
)

const (
	// ErrDone is returned for calls on a trace session that is already stopped.
	ErrDone = fault.Const("Trace already stopped")
	// ErrTracingToFile is returned for read calls on a trace session that is writting to a file on the device.
	ErrTracingToFile = fault.Const("Cannot read buffers, when tracing to a file")

	consumerService = "ConsumerPort"
	queryMethod     = "QueryServiceState"
	traceMethod     = "EnableTracing"
	startMethod     = "StartTracing"
	stopMethod      = "DisableTracing"
	readMethod      = "ReadBuffers"
)

// Client is a client ("consumer") of a Perfetto service.
type Client struct {
	conn    *client.Connection
	methods map[string]*client.Method
}

// NewClient returns a new client using the provided socket connection. The
// client takes ownership of the connection and invokes the provided cleanup
// on shutdown.
func NewClient(ctx context.Context, conn net.Conn, cleanup app.Cleanup) (*Client, error) {
	c, err := client.Connect(ctx, conn, cleanup)
	if err != nil {
		conn.Close()
		cleanup.Invoke(ctx)
		return nil, err
	}

	bind := client.NewBindSync(ctx)
	if err := c.Bind(ctx, consumerService, bind.Handler); err != nil {
		c.Close(ctx)
		return nil, err
	}
	methods, err := bind.Wait(ctx)
	if err != nil {
		c.Close(ctx)
		return nil, err
	}

	return &Client{
		conn:    c,
		methods: methods,
	}, nil
}

// Query queries the Perfetto service for producer and data source info and
// invokes the given callback on each received result. This is a streaming,
// synchronous RPC and the callback may be invoked multiple times.
func (c *Client) Query(ctx context.Context, cb func(*common.TracingServiceState) error) error {
	m, ok := c.methods[queryMethod]
	if !ok {
		return errors.New("Remote service doesn't have a query method")
	}

	query := client.NewQuerySync(ctx, cb)
	if err := c.conn.Invoke(ctx, m, &ipc.QueryServiceStateRequest{}, query.Handler); err != nil {
		return err
	}
	return query.Wait(ctx)
}

// TraceSession is the interface to a currently running Perfetto trace.
type TraceSession struct {
	conn  *client.Connection
	wait  task.Signal
	done  task.Task
	start *client.Method
	read  *client.Method
	stop  *client.Method
	out   *client.PacketWriter
	err   error
}

// Trace initiates a new Perfetto trace session using the given config. The
// trace buffers will be serialized to the given writer. This is an asynchronous
// RPC that can be controlled/waited on using the returned trace session.
func (c *Client) Trace(ctx context.Context, cfg *config.TraceConfig, out io.Writer) (*TraceSession, error) {
	trace, okTrace := c.methods[traceMethod]
	start, _ := c.methods[startMethod] // ignore missing start
	stop, okStop := c.methods[stopMethod]
	read, okRead := c.methods[readMethod]
	if !okTrace || !okStop || !okRead {
		return nil, errors.New("Remote service doesn't have the trace methods")
	}

	if !cfg.GetDeferredStart() {
		start = nil
	}
	if cfg.GetWriteIntoFile() {
		read = nil
	}

	wait, done := task.NewSignal()
	s := &TraceSession{
		conn:  c.conn,
		wait:  wait,
		done:  done,
		start: start,
		read:  read,
		stop:  stop,
		out:   client.NewPacketWriter(out),
	}

	h := client.NewTraceHandler(ctx, func(r *ipc.EnableTracingResponse, err error) {
		if err == nil && r.GetError() != "" {
			err = errors.New(r.GetError())
		}
		if !s.onResult(ctx, err) {
			if s.read == nil {
				s.done(ctx)
			} else {
				s.readBuffers(ctx, func(err error) {
					if !s.onResult(ctx, err) {
						s.done(ctx)
					}
				})
			}
		}
	})

	if err := c.conn.Invoke(ctx, trace, &ipc.EnableTracingRequest{TraceConfig: cfg}, h); err != nil {
		return nil, err
	}

	return s, nil
}

// Start starts the currently deferred trace of this session. Does nothing, if
// the Perfetto service doesn't support deferred tracing or the trace was not
// started in deferred mode.
func (s *TraceSession) Start(ctx context.Context) {
	if s.start == nil || s.wait.Fired() {
		// Ignore any starts if the trace was not defferred or after we've
		// already marked this session as done
		return
	}

	h := client.NewIgnoreHandler(ctx, func(err error) {
		s.onResult(ctx, err)
	})
	s.onResult(ctx, s.conn.Invoke(ctx, s.start, &ipc.StartTracingRequest{}, h))
}

// Read requests the service to transfer the buffered data and writes it into
// the output writer. This is a synchronous call that blocks until the service
// is done sending data and it has been written.
func (s *TraceSession) Read(ctx context.Context) error {
	if s.read == nil {
		return ErrTracingToFile
	} else if s.wait.Fired() {
		// Ignore any reads after we've already marked this session as done.
		return ErrDone
	}

	fail := make(chan error, 1)
	s.readBuffers(ctx, func(err error) {
		fail <- err
	})

	select {
	case err := <-fail:
		return err
	case <-task.ShouldStop(ctx):
		return task.StopReason(ctx)
	}
}

// Stop stops the currently running trace of this session.
func (s *TraceSession) Stop(ctx context.Context) {
	if s.wait.Fired() {
		// Ignore any stops after we've already marked this session as done.
		return
	}

	h := client.NewIgnoreHandler(ctx, func(err error) {
		s.onResult(ctx, err)
	})
	s.onResult(ctx, s.conn.Invoke(ctx, s.stop, &ipc.DisableTracingRequest{}, h))
}

// Wait waits for this trace session to finish and returns any error encountered
// during the trace.
func (s *TraceSession) Wait(ctx context.Context) error {
	if !s.wait.Wait(ctx) {
		return task.StopReason(ctx)
	}
	return s.err
}

func (s *TraceSession) onResult(ctx context.Context, err error) bool {
	if err != nil {
		if s.err == nil {
			s.err = err
		}
		s.done(ctx)
		return true
	}
	return false
}

func (s *TraceSession) readBuffers(ctx context.Context, done func(error)) {
	h := client.NewReadHandler(ctx, func(r *ipc.ReadBuffersResponse, more bool, err error) {
		if err != nil {
			done(err)
			return
		}

		if err := s.out.Write(r.Slices); err != nil {
			done(err)
			return
		}

		if !more {
			done(nil)
		}
	})
	s.onResult(ctx, s.conn.Invoke(ctx, s.read, &ipc.ReadBuffersRequest{}, h))
}

// Close closes the underlying connection to the Perfetto service of this client.
func (c *Client) Close(ctx context.Context) {
	c.conn.Close(ctx)
	c.conn = nil
}
<|endoftext|>