<commit_before>// Copyright 2019 Google LLC
//
// 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
//
//     https://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 v1alpha1

import (
	"testing"

	"github.com/google/kf/pkg/kf/testutil"
	serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"knative.dev/pkg/apis"
	duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
	apitesting "knative.dev/pkg/apis/testing"
)

func TestAppSucceeded(t *testing.T) {
	cases := []struct {
		name    string
		status  AppStatus
		isReady bool
	}{{
		name:    "empty status should not be ready",
		status:  AppStatus{},
		isReady: false,
	}, {
		name: "Different condition type should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   "Foo",
					Status: corev1.ConditionTrue,
				}},
			},
		},
		isReady: false,
	}, {
		name: "False condition status should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   AppConditionReady,
					Status: corev1.ConditionFalse,
				}},
			},
		},
		isReady: false,
	}, {
		name: "Unknown condition status should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   AppConditionReady,
					Status: corev1.ConditionUnknown,
				}},
			},
		},
		isReady: false,
	}, {
		name: "Missing condition status should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type: AppConditionReady,
				}},
			},
		},
		isReady: false,
	}, {
		name: "True condition status should be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   AppConditionReady,
					Status: corev1.ConditionTrue,
				}},
			},
		},
		isReady: true,
	}, {
		name: "Multiple conditions with ready status should be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   "Foo",
					Status: corev1.ConditionTrue,
				}, {
					Type:   AppConditionReady,
					Status: corev1.ConditionTrue,
				}},
			},
		},
		isReady: true,
	}, {
		name: "Multiple conditions with ready status false should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   "Foo",
					Status: corev1.ConditionTrue,
				}, {
					Type:   AppConditionReady,
					Status: corev1.ConditionFalse,
				}},
			},
		},
		isReady: false,
	}}

	for _, tc := range cases {
		testutil.AssertEqual(t, tc.name, tc.isReady, tc.status.IsReady())
	}
}

func initTestAppStatus(t *testing.T) *AppStatus {
	t.Helper()
	status := &AppStatus{}
	status.InitializeConditions()

	// sanity check
	apitesting.CheckConditionOngoing(status.duck(), AppConditionReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionSpaceReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionSourceReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionEnvVarSecretReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionKnativeServiceReady, t)

	return status
}

func happySource() *Source {
	return &Source{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-source-name",
		},
		Spec: SourceSpec{
			ServiceAccount: "builder-account",
			BuildpackBuild: SourceSpecBuildpackBuild{
				Source:           "gcr.io/my-registry/src-mysource",
				Stack:            "cflinuxfs3",
				BuildpackBuilder: "gcr.io/my-registry/my-builder:latest",
				Registry:         "gcr.io/my-registry",
			},
		},
		Status: SourceStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{
					{
						Type:   SourceConditionSucceeded,
						Status: corev1.ConditionTrue,
					},
				},
			},
			SourceStatusFields: SourceStatusFields{
				BuildName: "some-build-name",
				Image:     "some-container-image",
			},
		},
	}
}

func pendingSource() *Source {
	return &Source{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-source-name",
		},
		Spec: SourceSpec{
			ServiceAccount: "builder-account",
			BuildpackBuild: SourceSpecBuildpackBuild{
				Source:           "gcr.io/my-registry/src-mysource",
				Stack:            "cflinuxfs3",
				BuildpackBuilder: "gcr.io/my-registry/my-builder:latest",
				Registry:         "gcr.io/my-registry",
			},
		},
		Status: SourceStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{
					{
						Type:   SourceConditionSucceeded,
						Status: corev1.ConditionUnknown,
					},
				},
			},
			SourceStatusFields: SourceStatusFields{
				BuildName: "",
				Image:     "",
			},
		},
	}
}

func envVarSecret() *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-secret-name",
		},
		Data: map[string][]byte{
			"some-env-name": []byte("some-env-value"),
		},
	}
}

func happyKnativeService() *serving.Service {
	return &serving.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-service-name",
		},
		Status: serving.ServiceStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{
					{
						Type:   serving.ServiceConditionReady,
						Status: corev1.ConditionTrue,
					},
				},
			},
			ConfigurationStatusFields: serving.ConfigurationStatusFields{
				LatestReadyRevisionName:   "some-ready-revision-name",
				LatestCreatedRevisionName: "some-created-revision-name",
			},
			RouteStatusFields: serving.RouteStatusFields{
				URL: &apis.URL{
					Host: "example.com",
				},
			},
		},
	}
}

func pendingKnativeService() *serving.Service {
	return &serving.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-service-name",
		},
		Status: serving.ServiceStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{
					{
						Type:   serving.ServiceConditionReady,
						Status: corev1.ConditionUnknown,
					},
				},
			},
		},
	}
}

func TestAppHappyPath(t *testing.T) {
	status := initTestAppStatus(t)

	apitesting.CheckConditionOngoing(status.duck(), AppConditionReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionSpaceReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionSourceReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionEnvVarSecretReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionKnativeServiceReady, t)

	// space is healthy
	status.MarkSpaceHealthy()

	apitesting.CheckConditionSucceeded(status.duck(), AppConditionSpaceReady, t)

	// Source starts out pending
	status.PropagateSourceStatus(pendingSource())

	apitesting.CheckConditionOngoing(status.duck(), AppConditionSourceReady, t)
	testutil.AssertEqual(t, "LatestCreatedSourceName", "some-source-name", status.LatestCreatedSourceName)
	testutil.AssertEqual(t, "LatestReadySourceName", "", status.LatestReadySourceName)
	testutil.AssertEqual(t, "BuildName", "", status.SourceStatusFields.BuildName)
	testutil.AssertEqual(t, "Image", "", status.SourceStatusFields.Image)

	// Source succeeds
	status.PropagateSourceStatus(happySource())

	apitesting.CheckConditionSucceeded(status.duck(), AppConditionSourceReady, t)
	testutil.AssertEqual(t, "LatestReadySourceName", "some-source-name", status.LatestReadySourceName)
	testutil.AssertEqual(t, "BuildName", "some-build-name", status.SourceStatusFields.BuildName)
	testutil.AssertEqual(t, "Image", "some-container-image", status.SourceStatusFields.Image)

	// envVarSecret exists
	status.PropagateEnvVarSecretStatus(envVarSecret())

	apitesting.CheckConditionSucceeded(status.duck(), AppConditionEnvVarSecretReady, t)

	// Knative Serving starts out pending
	status.PropagateKnativeServiceStatus(pendingKnativeService())

	apitesting.CheckConditionOngoing(status.duck(), AppConditionReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionKnativeServiceReady, t)

	testutil.AssertEqual(t, "LatestReadyRevisionName", "", status.LatestReadyRevisionName)
	testutil.AssertEqual(t, "LatestCreatedRevisionName", "", status.LatestCreatedRevisionName)
	testutil.AssertEqual(t, "RouteStatusFields", serving.RouteStatusFields{}, status.RouteStatusFields)

	// Knative Serving is ready
	status.PropagateKnativeServiceStatus(happyKnativeService())

	apitesting.CheckConditionSucceeded(status.duck(), AppConditionReady, t)
	apitesting.CheckConditionSucceeded(status.duck(), AppConditionKnativeServiceReady, t)
	testutil.AssertEqual(t, "LatestReadyRevisionName", "some-ready-revision-name", status.LatestReadyRevisionName)
	testutil.AssertEqual(t, "LatestCreatedRevisionName", "some-created-revision-name", status.LatestCreatedRevisionName)
	testutil.AssertEqual(t, "RouteHost", "example.com", status.RouteStatusFields.URL.Host)
}

func TestAppStatus_lifecycle(t *testing.T) {
	cases := map[string]struct {
		Init func(status *AppStatus)

		ExpectSucceeded []apis.ConditionType
		ExpectFailed    []apis.ConditionType
		ExpectOngoing   []apis.ConditionType
	}{
		"happy path": {
			Init: func(status *AppStatus) {
				status.MarkSpaceHealthy()
				status.PropagateSourceStatus(happySource())
				status.PropagateEnvVarSecretStatus(envVarSecret())
				status.PropagateKnativeServiceStatus(happyKnativeService())
			},
			ExpectSucceeded: []apis.ConditionType{
				AppConditionReady,
				AppConditionSpaceReady,
				AppConditionSourceReady,
				AppConditionEnvVarSecretReady,
				AppConditionKnativeServiceReady,
			},
		},
		"space unhealthy": {
			Init: func(status *AppStatus) {
				status.MarkSpaceUnhealthy("Terminating", "Namespace is terminating")
			},
			ExpectFailed: []apis.ConditionType{
				AppConditionReady,
				AppConditionSpaceReady,
			},
		},
	}

	for tn, tc := range cases {
		t.Run(tn, func(t *testing.T) {
			status := initTestAppStatus(t)

			tc.Init(status)

			for _, exp := range tc.ExpectFailed {
				apitesting.CheckConditionFailed(status.duck(), exp, t)
			}

			for _, exp := range tc.ExpectOngoing {
				apitesting.CheckConditionOngoing(status.duck(), exp, t)
			}

			for _, exp := range tc.ExpectSucceeded {
				apitesting.CheckConditionSucceeded(status.duck(), exp, t)
			}
		})
	}
}
<commit_msg>fix broken master (#528)<commit_after>// Copyright 2019 Google LLC
//
// 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
//
//     https://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 v1alpha1

import (
	"testing"

	"github.com/google/kf/pkg/kf/testutil"
	serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"knative.dev/pkg/apis"
	duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
	apitesting "knative.dev/pkg/apis/testing"
)

func TestAppSucceeded(t *testing.T) {
	cases := []struct {
		name    string
		status  AppStatus
		isReady bool
	}{{
		name:    "empty status should not be ready",
		status:  AppStatus{},
		isReady: false,
	}, {
		name: "Different condition type should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   "Foo",
					Status: corev1.ConditionTrue,
				}},
			},
		},
		isReady: false,
	}, {
		name: "False condition status should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   AppConditionReady,
					Status: corev1.ConditionFalse,
				}},
			},
		},
		isReady: false,
	}, {
		name: "Unknown condition status should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   AppConditionReady,
					Status: corev1.ConditionUnknown,
				}},
			},
		},
		isReady: false,
	}, {
		name: "Missing condition status should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type: AppConditionReady,
				}},
			},
		},
		isReady: false,
	}, {
		name: "True condition status should be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   AppConditionReady,
					Status: corev1.ConditionTrue,
				}},
			},
		},
		isReady: true,
	}, {
		name: "Multiple conditions with ready status should be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   "Foo",
					Status: corev1.ConditionTrue,
				}, {
					Type:   AppConditionReady,
					Status: corev1.ConditionTrue,
				}},
			},
		},
		isReady: true,
	}, {
		name: "Multiple conditions with ready status false should not be ready",
		status: AppStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{{
					Type:   "Foo",
					Status: corev1.ConditionTrue,
				}, {
					Type:   AppConditionReady,
					Status: corev1.ConditionFalse,
				}},
			},
		},
		isReady: false,
	}}

	for _, tc := range cases {
		testutil.AssertEqual(t, tc.name, tc.isReady, tc.status.IsReady())
	}
}

func initTestAppStatus(t *testing.T) *AppStatus {
	t.Helper()
	status := &AppStatus{}
	status.InitializeConditions()

	// sanity check
	apitesting.CheckConditionOngoing(status.duck(), AppConditionReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionSpaceReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionSourceReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionEnvVarSecretReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionKnativeServiceReady, t)

	return status
}

func happySource() *Source {
	return &Source{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-source-name",
		},
		Spec: SourceSpec{
			ServiceAccount: "builder-account",
			BuildpackBuild: SourceSpecBuildpackBuild{
				Source:           "gcr.io/my-registry/src-mysource",
				Stack:            "cflinuxfs3",
				BuildpackBuilder: "gcr.io/my-registry/my-builder:latest",
				Image:            "gcr.io/my-registry/output:123",
			},
		},
		Status: SourceStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{
					{
						Type:   SourceConditionSucceeded,
						Status: corev1.ConditionTrue,
					},
				},
			},
			SourceStatusFields: SourceStatusFields{
				BuildName: "some-build-name",
				Image:     "some-container-image",
			},
		},
	}
}

func pendingSource() *Source {
	return &Source{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-source-name",
		},
		Spec: SourceSpec{
			ServiceAccount: "builder-account",
			BuildpackBuild: SourceSpecBuildpackBuild{
				Source:           "gcr.io/my-registry/src-mysource",
				Stack:            "cflinuxfs3",
				BuildpackBuilder: "gcr.io/my-registry/my-builder:latest",
				Image:            "gcr.io/my-registry/output:123",
			},
		},
		Status: SourceStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{
					{
						Type:   SourceConditionSucceeded,
						Status: corev1.ConditionUnknown,
					},
				},
			},
			SourceStatusFields: SourceStatusFields{
				BuildName: "",
				Image:     "",
			},
		},
	}
}

func envVarSecret() *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-secret-name",
		},
		Data: map[string][]byte{
			"some-env-name": []byte("some-env-value"),
		},
	}
}

func happyKnativeService() *serving.Service {
	return &serving.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-service-name",
		},
		Status: serving.ServiceStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{
					{
						Type:   serving.ServiceConditionReady,
						Status: corev1.ConditionTrue,
					},
				},
			},
			ConfigurationStatusFields: serving.ConfigurationStatusFields{
				LatestReadyRevisionName:   "some-ready-revision-name",
				LatestCreatedRevisionName: "some-created-revision-name",
			},
			RouteStatusFields: serving.RouteStatusFields{
				URL: &apis.URL{
					Host: "example.com",
				},
			},
		},
	}
}

func pendingKnativeService() *serving.Service {
	return &serving.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name: "some-service-name",
		},
		Status: serving.ServiceStatus{
			Status: duckv1beta1.Status{
				Conditions: duckv1beta1.Conditions{
					{
						Type:   serving.ServiceConditionReady,
						Status: corev1.ConditionUnknown,
					},
				},
			},
		},
	}
}

func TestAppHappyPath(t *testing.T) {
	status := initTestAppStatus(t)

	apitesting.CheckConditionOngoing(status.duck(), AppConditionReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionSpaceReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionSourceReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionEnvVarSecretReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionKnativeServiceReady, t)

	// space is healthy
	status.MarkSpaceHealthy()

	apitesting.CheckConditionSucceeded(status.duck(), AppConditionSpaceReady, t)

	// Source starts out pending
	status.PropagateSourceStatus(pendingSource())

	apitesting.CheckConditionOngoing(status.duck(), AppConditionSourceReady, t)
	testutil.AssertEqual(t, "LatestCreatedSourceName", "some-source-name", status.LatestCreatedSourceName)
	testutil.AssertEqual(t, "LatestReadySourceName", "", status.LatestReadySourceName)
	testutil.AssertEqual(t, "BuildName", "", status.SourceStatusFields.BuildName)
	testutil.AssertEqual(t, "Image", "", status.SourceStatusFields.Image)

	// Source succeeds
	status.PropagateSourceStatus(happySource())

	apitesting.CheckConditionSucceeded(status.duck(), AppConditionSourceReady, t)
	testutil.AssertEqual(t, "LatestReadySourceName", "some-source-name", status.LatestReadySourceName)
	testutil.AssertEqual(t, "BuildName", "some-build-name", status.SourceStatusFields.BuildName)
	testutil.AssertEqual(t, "Image", "some-container-image", status.SourceStatusFields.Image)

	// envVarSecret exists
	status.PropagateEnvVarSecretStatus(envVarSecret())

	apitesting.CheckConditionSucceeded(status.duck(), AppConditionEnvVarSecretReady, t)

	// Knative Serving starts out pending
	status.PropagateKnativeServiceStatus(pendingKnativeService())

	apitesting.CheckConditionOngoing(status.duck(), AppConditionReady, t)
	apitesting.CheckConditionOngoing(status.duck(), AppConditionKnativeServiceReady, t)

	testutil.AssertEqual(t, "LatestReadyRevisionName", "", status.LatestReadyRevisionName)
	testutil.AssertEqual(t, "LatestCreatedRevisionName", "", status.LatestCreatedRevisionName)
	testutil.AssertEqual(t, "RouteStatusFields", serving.RouteStatusFields{}, status.RouteStatusFields)

	// Knative Serving is ready
	status.PropagateKnativeServiceStatus(happyKnativeService())

	apitesting.CheckConditionSucceeded(status.duck(), AppConditionReady, t)
	apitesting.CheckConditionSucceeded(status.duck(), AppConditionKnativeServiceReady, t)
	testutil.AssertEqual(t, "LatestReadyRevisionName", "some-ready-revision-name", status.LatestReadyRevisionName)
	testutil.AssertEqual(t, "LatestCreatedRevisionName", "some-created-revision-name", status.LatestCreatedRevisionName)
	testutil.AssertEqual(t, "RouteHost", "example.com", status.RouteStatusFields.URL.Host)
}

func TestAppStatus_lifecycle(t *testing.T) {
	cases := map[string]struct {
		Init func(status *AppStatus)

		ExpectSucceeded []apis.ConditionType
		ExpectFailed    []apis.ConditionType
		ExpectOngoing   []apis.ConditionType
	}{
		"happy path": {
			Init: func(status *AppStatus) {
				status.MarkSpaceHealthy()
				status.PropagateSourceStatus(happySource())
				status.PropagateEnvVarSecretStatus(envVarSecret())
				status.PropagateKnativeServiceStatus(happyKnativeService())
			},
			ExpectSucceeded: []apis.ConditionType{
				AppConditionReady,
				AppConditionSpaceReady,
				AppConditionSourceReady,
				AppConditionEnvVarSecretReady,
				AppConditionKnativeServiceReady,
			},
		},
		"space unhealthy": {
			Init: func(status *AppStatus) {
				status.MarkSpaceUnhealthy("Terminating", "Namespace is terminating")
			},
			ExpectFailed: []apis.ConditionType{
				AppConditionReady,
				AppConditionSpaceReady,
			},
		},
	}

	for tn, tc := range cases {
		t.Run(tn, func(t *testing.T) {
			status := initTestAppStatus(t)

			tc.Init(status)

			for _, exp := range tc.ExpectFailed {
				apitesting.CheckConditionFailed(status.duck(), exp, t)
			}

			for _, exp := range tc.ExpectOngoing {
				apitesting.CheckConditionOngoing(status.duck(), exp, t)
			}

			for _, exp := range tc.ExpectSucceeded {
				apitesting.CheckConditionSucceeded(status.duck(), exp, t)
			}
		})
	}
}
<|endoftext|>


<commit_before>package engine

import (
	"context"
	"fmt"
	"time"

	"code.cloudfoundry.org/clock"
	"code.cloudfoundry.org/lager/lagerctx"
	"github.com/concourse/concourse/atc"
	"github.com/concourse/concourse/atc/db"
	"github.com/concourse/concourse/atc/db/lock"
	"github.com/concourse/concourse/atc/event"
	"github.com/concourse/concourse/atc/exec"
	"github.com/concourse/concourse/atc/policy"
	"github.com/concourse/concourse/atc/worker"
)

//counterfeiter:generate . RateLimiter
type RateLimiter interface {
	Wait(context.Context) error
}

func NewCheckDelegate(
	build db.Build,
	plan atc.Plan,
	state exec.RunState,
	clock clock.Clock,
	limiter RateLimiter,
	policyChecker policy.Checker,
	artifactSourcer worker.ArtifactSourcer,
) exec.CheckDelegate {
	return &checkDelegate{
		BuildStepDelegate: NewBuildStepDelegate(build, plan.ID, state, clock, policyChecker, artifactSourcer),

		build:       build,
		plan:        plan.Check,
		eventOrigin: event.Origin{ID: event.OriginID(plan.ID)},
		clock:       clock,

		limiter: limiter,
	}
}

type checkDelegate struct {
	exec.BuildStepDelegate

	build       db.Build
	plan        *atc.CheckPlan
	eventOrigin event.Origin
	clock       clock.Clock

	// stashed away just so we don't have to query them multiple times
	cachedPipeline     db.Pipeline
	cachedResource     db.Resource
	cachedResourceType db.ResourceType
	cachedPrototype    db.Prototype

	limiter RateLimiter
}

func (d *checkDelegate) FindOrCreateScope(config db.ResourceConfig) (db.ResourceConfigScope, error) {
	resource, _, err := d.resource()
	if err != nil {
		return nil, fmt.Errorf("get resource: %w", err)
	}

	scope, err := config.FindOrCreateScope(resource) // ignore found, nil is ok
	if err != nil {
		return nil, fmt.Errorf("find or create scope: %w", err)
	}

	return scope, nil
}

// WaitToRun decides if a check should really run or just reuse a previous result, and acquires
// a check lock accordingly. There are three types of checks, each reflects to a different behavior:
// 1) A Lidar triggered checks should always run once reach to next check time;
// 2) A manually triggered checks may reuse a previous result if the last check succeeded and began
// later than the current check build's create time.
// 3) A step embedded check may reuse a previous step if the last check succeeded and finished later
// than the current build started.
func (d *checkDelegate) WaitToRun(ctx context.Context, scope db.ResourceConfigScope) (lock.Lock, bool, error) {
	logger := lagerctx.FromContext(ctx)

	// rate limit periodic resource checks so worker load (plus load on external
	// services) isn't too spiky
	if !d.build.IsManuallyTriggered() && d.plan.IsPeriodic() {
		err := d.limiter.Wait(ctx)
		if err != nil {
			return nil, false, fmt.Errorf("rate limit: %w", err)
		}
	}

	var err error

	var interval time.Duration
	if d.plan.Interval != "" {
		interval, err = time.ParseDuration(d.plan.Interval)
		if err != nil {
			return nil, false, err
		}
	}

	var lock lock.Lock = lock.NoopLock{}
	if d.plan.IsPeriodic() {
		for {
			var acquired bool
			lock, acquired, err = scope.AcquireResourceCheckingLock(logger)
			if err != nil {
				return nil, false, fmt.Errorf("acquire lock: %w", err)
			}

			if acquired {
				break
			}

			d.clock.Sleep(time.Second)
		}
	}

	lastCheck, err := scope.LastCheck()
	if err != nil {
		if releaseErr := lock.Release(); releaseErr != nil {
			logger.Error("failed-to-release-lock", releaseErr)
		}
		return nil, false, err
	}

	shouldRun := false
	if !d.plan.IsPeriodic() {
		if !lastCheck.Succeeded || lastCheck.EndTime.Before(d.build.StartTime()) {
			shouldRun = true
		}
	} else if d.build.IsManuallyTriggered() {
		// If a manually triggered check takes a from version, then it should be run.
		if d.plan.FromVersion != nil {
			shouldRun = true
		} else {
			// ignore interval for manually triggered builds.
			// avoid running redundant checks
			shouldRun = !lastCheck.Succeeded || d.build.CreateTime().After(lastCheck.StartTime)
		}
	} else {
		shouldRun = !d.clock.Now().Before(lastCheck.EndTime.Add(interval))
	}

	// XXX(check-refactor): we could add an else{} case and potentially sleep
	// here until runAt is reached.
	//
	// then the check build queueing logic is to just make sure there's a build
	// running for every resource, without having to check if intervals have
	// elapsed.
	//
	// this could be expanded upon to short-circuit the waiting with events
	// triggered by webhooks so that webhooks are super responsive: rather than
	// queueing a build, it would just wake up a goroutine.

	if !shouldRun {
		err := lock.Release()
		if err != nil {
			return nil, false, fmt.Errorf("release lock: %w", err)
		}

		return nil, false, nil
	}

	return lock, true, nil
}

func (d *checkDelegate) PointToCheckedConfig(scope db.ResourceConfigScope) error {
	resource, found, err := d.resource()
	if err != nil {
		return fmt.Errorf("get resource: %w", err)
	}

	if found {
		err := resource.SetResourceConfigScope(scope)
		if err != nil {
			return fmt.Errorf("set resource scope: %w", err)
		}
	}

	resourceType, found, err := d.resourceType()
	if err != nil {
		return fmt.Errorf("get resource type: %w", err)
	}

	if found {
		err := resourceType.SetResourceConfigScope(scope)
		if err != nil {
			return fmt.Errorf("set resource type scope: %w", err)
		}
	}

	prototype, found, err := d.prototype()
	if err != nil {
		return fmt.Errorf("get prototype: %w", err)
	}

	if found {
		err := prototype.SetResourceConfigScope(scope)
		if err != nil {
			return fmt.Errorf("set prototype scope: %w", err)
		}
	}

	return nil
}

func (d *checkDelegate) pipeline() (db.Pipeline, error) {
	if d.cachedPipeline != nil {
		return d.cachedPipeline, nil
	}

	pipeline, found, err := d.build.Pipeline()
	if err != nil {
		return nil, fmt.Errorf("get build pipeline: %w", err)
	}

	if !found {
		return nil, fmt.Errorf("pipeline not found")
	}

	d.cachedPipeline = pipeline

	return d.cachedPipeline, nil
}

func (d *checkDelegate) resource() (db.Resource, bool, error) {
	if d.plan.Resource == "" {
		return nil, false, nil
	}

	if d.cachedResource != nil {
		return d.cachedResource, true, nil
	}

	pipeline, err := d.pipeline()
	if err != nil {
		return nil, false, err
	}

	resource, found, err := pipeline.Resource(d.plan.Resource)
	if err != nil {
		return nil, false, fmt.Errorf("get pipeline resource: %w", err)
	}

	if !found {
		return nil, false, fmt.Errorf("resource '%s' deleted", d.plan.Resource)
	}

	d.cachedResource = resource

	return d.cachedResource, true, nil
}

func (d *checkDelegate) resourceType() (db.ResourceType, bool, error) {
	if d.plan.ResourceType == "" {
		return nil, false, nil
	}

	if d.cachedResourceType != nil {
		return d.cachedResourceType, true, nil
	}

	pipeline, err := d.pipeline()
	if err != nil {
		return nil, false, err
	}

	resourceType, found, err := pipeline.ResourceType(d.plan.ResourceType)
	if err != nil {
		return nil, false, fmt.Errorf("get pipeline resource type: %w", err)
	}

	if !found {
		return nil, false, fmt.Errorf("resource type '%s' deleted", d.plan.ResourceType)
	}

	d.cachedResourceType = resourceType

	return d.cachedResourceType, true, nil
}

func (d *checkDelegate) prototype() (db.Prototype, bool, error) {
	if d.plan.Prototype == "" {
		return nil, false, nil
	}

	if d.cachedPrototype != nil {
		return d.cachedPrototype, true, nil
	}

	pipeline, err := d.pipeline()
	if err != nil {
		return nil, false, err
	}

	prototype, found, err := pipeline.Prototype(d.plan.Prototype)
	if err != nil {
		return nil, false, fmt.Errorf("get pipeline prototype: %w", err)
	}

	if !found {
		return nil, false, fmt.Errorf("prototype '%s' deleted", d.plan.Prototype)
	}

	d.cachedPrototype = prototype

	return d.cachedPrototype, true, nil
}
<commit_msg>B: don't rate limit resource type checks<commit_after>package engine

import (
	"context"
	"fmt"
	"time"

	"code.cloudfoundry.org/clock"
	"code.cloudfoundry.org/lager/lagerctx"
	"github.com/concourse/concourse/atc"
	"github.com/concourse/concourse/atc/db"
	"github.com/concourse/concourse/atc/db/lock"
	"github.com/concourse/concourse/atc/event"
	"github.com/concourse/concourse/atc/exec"
	"github.com/concourse/concourse/atc/policy"
	"github.com/concourse/concourse/atc/worker"
)

//counterfeiter:generate . RateLimiter
type RateLimiter interface {
	Wait(context.Context) error
}

func NewCheckDelegate(
	build db.Build,
	plan atc.Plan,
	state exec.RunState,
	clock clock.Clock,
	limiter RateLimiter,
	policyChecker policy.Checker,
	artifactSourcer worker.ArtifactSourcer,
) exec.CheckDelegate {
	return &checkDelegate{
		BuildStepDelegate: NewBuildStepDelegate(build, plan.ID, state, clock, policyChecker, artifactSourcer),

		build:       build,
		plan:        plan.Check,
		eventOrigin: event.Origin{ID: event.OriginID(plan.ID)},
		clock:       clock,

		limiter: limiter,
	}
}

type checkDelegate struct {
	exec.BuildStepDelegate

	build       db.Build
	plan        *atc.CheckPlan
	eventOrigin event.Origin
	clock       clock.Clock

	// stashed away just so we don't have to query them multiple times
	cachedPipeline     db.Pipeline
	cachedResource     db.Resource
	cachedResourceType db.ResourceType
	cachedPrototype    db.Prototype

	limiter RateLimiter
}

func (d *checkDelegate) FindOrCreateScope(config db.ResourceConfig) (db.ResourceConfigScope, error) {
	resource, _, err := d.resource()
	if err != nil {
		return nil, fmt.Errorf("get resource: %w", err)
	}

	scope, err := config.FindOrCreateScope(resource) // ignore found, nil is ok
	if err != nil {
		return nil, fmt.Errorf("find or create scope: %w", err)
	}

	return scope, nil
}

// WaitToRun decides if a check should really run or just reuse a previous result, and acquires
// a check lock accordingly. There are three types of checks, each reflects to a different behavior:
// 1) A Lidar triggered checks should always run once reach to next check time;
// 2) A manually triggered checks may reuse a previous result if the last check succeeded and began
// later than the current check build's create time.
// 3) A step embedded check may reuse a previous step if the last check succeeded and finished later
// than the current build started.
func (d *checkDelegate) WaitToRun(ctx context.Context, scope db.ResourceConfigScope) (lock.Lock, bool, error) {
	logger := lagerctx.FromContext(ctx)

	// rate limit periodic resource checks so worker load (plus load on
	// external services) isn't too spiky. note that we don't rate limit
	// resource type or prototype checks, because they are created every time a
	// resource is used (rather than periodically).
	if !d.build.IsManuallyTriggered() && d.plan.Resource != "" {
		err := d.limiter.Wait(ctx)
		if err != nil {
			return nil, false, fmt.Errorf("rate limit: %w", err)
		}
	}

	var err error

	var interval time.Duration
	if d.plan.Interval != "" {
		interval, err = time.ParseDuration(d.plan.Interval)
		if err != nil {
			return nil, false, err
		}
	}

	var lock lock.Lock = lock.NoopLock{}
	if d.plan.IsPeriodic() {
		for {
			var acquired bool
			lock, acquired, err = scope.AcquireResourceCheckingLock(logger)
			if err != nil {
				return nil, false, fmt.Errorf("acquire lock: %w", err)
			}

			if acquired {
				break
			}

			d.clock.Sleep(time.Second)
		}
	}

	lastCheck, err := scope.LastCheck()
	if err != nil {
		if releaseErr := lock.Release(); releaseErr != nil {
			logger.Error("failed-to-release-lock", releaseErr)
		}
		return nil, false, err
	}

	shouldRun := false
	if !d.plan.IsPeriodic() {
		if !lastCheck.Succeeded || lastCheck.EndTime.Before(d.build.StartTime()) {
			shouldRun = true
		}
	} else if d.build.IsManuallyTriggered() {
		// If a manually triggered check takes a from version, then it should be run.
		if d.plan.FromVersion != nil {
			shouldRun = true
		} else {
			// ignore interval for manually triggered builds.
			// avoid running redundant checks
			shouldRun = !lastCheck.Succeeded || d.build.CreateTime().After(lastCheck.StartTime)
		}
	} else {
		shouldRun = !d.clock.Now().Before(lastCheck.EndTime.Add(interval))
	}

	// XXX(check-refactor): we could add an else{} case and potentially sleep
	// here until runAt is reached.
	//
	// then the check build queueing logic is to just make sure there's a build
	// running for every resource, without having to check if intervals have
	// elapsed.
	//
	// this could be expanded upon to short-circuit the waiting with events
	// triggered by webhooks so that webhooks are super responsive: rather than
	// queueing a build, it would just wake up a goroutine.

	if !shouldRun {
		err := lock.Release()
		if err != nil {
			return nil, false, fmt.Errorf("release lock: %w", err)
		}

		return nil, false, nil
	}

	return lock, true, nil
}

func (d *checkDelegate) PointToCheckedConfig(scope db.ResourceConfigScope) error {
	resource, found, err := d.resource()
	if err != nil {
		return fmt.Errorf("get resource: %w", err)
	}

	if found {
		err := resource.SetResourceConfigScope(scope)
		if err != nil {
			return fmt.Errorf("set resource scope: %w", err)
		}
	}

	resourceType, found, err := d.resourceType()
	if err != nil {
		return fmt.Errorf("get resource type: %w", err)
	}

	if found {
		err := resourceType.SetResourceConfigScope(scope)
		if err != nil {
			return fmt.Errorf("set resource type scope: %w", err)
		}
	}

	prototype, found, err := d.prototype()
	if err != nil {
		return fmt.Errorf("get prototype: %w", err)
	}

	if found {
		err := prototype.SetResourceConfigScope(scope)
		if err != nil {
			return fmt.Errorf("set prototype scope: %w", err)
		}
	}

	return nil
}

func (d *checkDelegate) pipeline() (db.Pipeline, error) {
	if d.cachedPipeline != nil {
		return d.cachedPipeline, nil
	}

	pipeline, found, err := d.build.Pipeline()
	if err != nil {
		return nil, fmt.Errorf("get build pipeline: %w", err)
	}

	if !found {
		return nil, fmt.Errorf("pipeline not found")
	}

	d.cachedPipeline = pipeline

	return d.cachedPipeline, nil
}

func (d *checkDelegate) resource() (db.Resource, bool, error) {
	if d.plan.Resource == "" {
		return nil, false, nil
	}

	if d.cachedResource != nil {
		return d.cachedResource, true, nil
	}

	pipeline, err := d.pipeline()
	if err != nil {
		return nil, false, err
	}

	resource, found, err := pipeline.Resource(d.plan.Resource)
	if err != nil {
		return nil, false, fmt.Errorf("get pipeline resource: %w", err)
	}

	if !found {
		return nil, false, fmt.Errorf("resource '%s' deleted", d.plan.Resource)
	}

	d.cachedResource = resource

	return d.cachedResource, true, nil
}

func (d *checkDelegate) resourceType() (db.ResourceType, bool, error) {
	if d.plan.ResourceType == "" {
		return nil, false, nil
	}

	if d.cachedResourceType != nil {
		return d.cachedResourceType, true, nil
	}

	pipeline, err := d.pipeline()
	if err != nil {
		return nil, false, err
	}

	resourceType, found, err := pipeline.ResourceType(d.plan.ResourceType)
	if err != nil {
		return nil, false, fmt.Errorf("get pipeline resource type: %w", err)
	}

	if !found {
		return nil, false, fmt.Errorf("resource type '%s' deleted", d.plan.ResourceType)
	}

	d.cachedResourceType = resourceType

	return d.cachedResourceType, true, nil
}

func (d *checkDelegate) prototype() (db.Prototype, bool, error) {
	if d.plan.Prototype == "" {
		return nil, false, nil
	}

	if d.cachedPrototype != nil {
		return d.cachedPrototype, true, nil
	}

	pipeline, err := d.pipeline()
	if err != nil {
		return nil, false, err
	}

	prototype, found, err := pipeline.Prototype(d.plan.Prototype)
	if err != nil {
		return nil, false, fmt.Errorf("get pipeline prototype: %w", err)
	}

	if !found {
		return nil, false, fmt.Errorf("prototype '%s' deleted", d.plan.Prototype)
	}

	d.cachedPrototype = prototype

	return d.cachedPrototype, true, nil
}
<|endoftext|>


<commit_before>package v5

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

	"github.com/golang/glog"

	"github.com/jetstack-experimental/navigator/pkg/apis/navigator/v1alpha1"
)

const (
	elasticsearchConfigSubDir = "elasticsearch/config"
)

func (p *Pilot) WriteConfig(pilot *v1alpha1.Pilot) error {
	esConfigPath := fmt.Sprintf("%s/%s", p.Options.ConfigDir, elasticsearchConfigSubDir)
	files, err := ioutil.ReadDir(esConfigPath)
	if err != nil {
		return fmt.Errorf("error listing provided config files: %s", err.Error())
	}
	for _, info := range files {
		path := filepath.Join(esConfigPath, info.Name())
		path, err = filepath.EvalSymlinks(path)
		if err != nil {
			return fmt.Errorf("error evaluating symlinks in path %q: %s", path, err.Error())
		}
		glog.V(2).Infof("Considering file %q (path: %q) when writing elasticsearch config", info.Name(), path)
		// re-check info after evaluating symlinks
		info, err = os.Stat(path)
		if err != nil {
			return fmt.Errorf("error getting info for path %q: %s", path, err.Error())
		}
		if info.IsDir() {
			continue
		}
		relPath, err := filepath.Rel(esConfigPath, path)
		if err != nil {
			return err
		}
		dstPath := fmt.Sprintf("%s/%s", p.Options.ElasticsearchOptions.ConfigDir, relPath)
		glog.V(2).Infof("Relative destination path %q, destination path %q")
		relDir := filepath.Dir(relPath)
		glog.V(2).Infof("Ensuring directory %q exists")
		stat, err := os.Stat(relDir)
		if os.IsNotExist(err) {
			err = os.MkdirAll(relDir, 0644)
			if err != nil {
				return err
			}
			stat, err = os.Stat(relDir)
		}
		if err != nil {
			return err
		}
		if !stat.IsDir() {
			return fmt.Errorf("path '%s' exists and is not a directory", relDir)
		}
		if err = copyFileContents(path, dstPath); err != nil {
			return err
		}
	}
	if err != nil {
		return fmt.Errorf("error writing config file: %s", err.Error())
	}

	return nil
}

// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
// From: https://stackoverflow.com/a/21067803
func copyFileContents(src, dst string) (err error) {
	in, err := os.Open(src)
	if err != nil {
		return
	}
	defer in.Close()
	out, err := os.Create(dst)
	if err != nil {
		return
	}
	defer func() {
		cerr := out.Close()
		if err == nil {
			err = cerr
		}
	}()
	if _, err = io.Copy(out, in); err != nil {
		return
	}
	err = out.Sync()
	return
}
<commit_msg>Remove dir creation logic and fix file copy<commit_after>package v5

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

	"github.com/golang/glog"

	"github.com/jetstack-experimental/navigator/pkg/apis/navigator/v1alpha1"
)

const (
	elasticsearchConfigSubDir = "elasticsearch/config"
)

func (p *Pilot) WriteConfig(pilot *v1alpha1.Pilot) error {
	esConfigPath := fmt.Sprintf("%s/%s", p.Options.ConfigDir, elasticsearchConfigSubDir)
	files, err := ioutil.ReadDir(esConfigPath)
	if err != nil {
		return fmt.Errorf("error listing provided config files: %s", err.Error())
	}
	for _, info := range files {
		path := filepath.Join(esConfigPath, info.Name())
		path, err = filepath.EvalSymlinks(path)
		if err != nil {
			return fmt.Errorf("error evaluating symlinks in path %q: %s", path, err.Error())
		}
		glog.V(2).Infof("Considering file %q (path: %q) when writing elasticsearch config", info.Name(), path)
		// re-check info after evaluating symlinks
		info, err = os.Stat(path)
		if err != nil {
			return fmt.Errorf("error getting info for path %q: %s", path, err.Error())
		}
		if info.IsDir() {
			continue
		}
		dstPath := fmt.Sprintf("%s/%s", p.Options.ElasticsearchOptions.ConfigDir, info.Name())
		glog.V(2).Infof("Copying config file from %q to %q", path, dstPath)
		if err = copyFileContents(path, dstPath); err != nil {
			return err
		}
	}
	if err != nil {
		return fmt.Errorf("error writing config file: %s", err.Error())
	}

	return nil
}

// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
// From: https://stackoverflow.com/a/21067803
func copyFileContents(src, dst string) (err error) {
	in, err := os.Open(src)
	if err != nil {
		return
	}
	defer in.Close()
	out, err := os.Create(dst)
	if err != nil {
		return
	}
	defer func() {
		cerr := out.Close()
		if err == nil {
			err = cerr
		}
	}()
	if _, err = io.Copy(out, in); err != nil {
		return
	}
	err = out.Sync()
	return
}
<|endoftext|>


<commit_before>package wrphttp

import (
	"net/http"
	"net/url"
	"time"

	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/middleware"
	"github.com/Comcast/webpa-common/middleware/fanout"
	"github.com/Comcast/webpa-common/tracing"
	"github.com/Comcast/webpa-common/transport/transporthttp"
	"github.com/Comcast/webpa-common/wrp"
	"github.com/Comcast/webpa-common/xhttp"
	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/log"
	gokithttp "github.com/go-kit/kit/transport/http"
)

const (
	DefaultMethod                            = "POST"
	DefaultEndpoint                          = "http://localhost:7000/api/v2/device/send"
	DefaultMaxIdleConnsPerHost               = 20
	DefaultFanoutTimeout       time.Duration = 45 * time.Second
	DefaultClientTimeout       time.Duration = 30 * time.Second
	DefaultMaxClients          int64         = 10000
	DefaultConcurrency                       = 1000
	DefaultEncoderPoolSize                   = 100
	DefaultDecoderPoolSize                   = 100
)

// FanoutOptions describe the options available for a go-kit HTTP server that does fanout via fanout.New.
type FanoutOptions struct {
	// Logger is the go-kit logger to use when creating the service fanout.  If not set, logging.DefaultLogger is used.
	Logger log.Logger `json:"-"`

	// Method is the HTTP method to use for all endpoints.  If not set, DefaultMethod is used.
	Method string `json:"method,omitempty"`

	// Endpoints are the URLs for each endpoint to fan out to.  If not set, DefaultEndpoint is used.
	Endpoints []string `json:"endpoints,omitempty"`

	// Authorization is the Basic Auth token.  There is no default for this field.
	Authorization string `json:"authorization"`

	// Transport is the http.Client transport
	Transport http.Transport `json:"transport"`

	// FanoutTimeout is the timeout for the entire fanout operation.  If not supplied, DefaultFanoutTimeout is used.
	FanoutTimeout time.Duration `json:"timeout"`

	// ClientTimeout is the http.Client Timeout.  If not set, DefaultClientTimeout is used.
	ClientTimeout time.Duration `json:"clientTimeout"`

	// MaxClients is the maximum number of concurrent clients that can be using the fanout.  This should be set to
	// something larger than the Concurrency field.
	MaxClients int64 `json:"maxClients"`

	// Concurrency is the maximum number of concurrent fanouts allowed.  This is enforced via a Concurrent middleware.
	// If this is not set, DefaultConcurrency is used.
	Concurrency int `json:"concurrency"`

	// EncoderPoolSize is the size of the WRP encoder pool.  If not set, DefaultEncoderPoolSize is used.
	EncoderPoolSize int

	// DecoderPoolSize is the size of the WRP encoder pool.  If not set, DefaultDecoderPoolSize is used.
	DecoderPoolSize int

	// Middleware is the extra Middleware to append, which can (and often is) empty
	Middleware []endpoint.Middleware `json:"-"`
}

func (f *FanoutOptions) logger() log.Logger {
	if f != nil && f.Logger != nil {
		return f.Logger
	}

	return logging.DefaultLogger()
}

func (f *FanoutOptions) method() string {
	if f != nil && len(f.Method) > 0 {
		return f.Method
	}

	return DefaultMethod
}

func (f *FanoutOptions) endpoints() []string {
	if f != nil && len(f.Endpoints) > 0 {
		return f.Endpoints
	}

	return []string{DefaultEndpoint}
}

func (f *FanoutOptions) authorization() string {
	if f != nil && len(f.Authorization) > 0 {
		return f.Authorization
	}

	return ""
}

func (f *FanoutOptions) urls() ([]*url.URL, error) {
	var urls []*url.URL
	for _, endpoint := range f.endpoints() {
		url, err := url.Parse(endpoint)
		if err != nil {
			return nil, err
		}

		urls = append(urls, url)
	}

	return urls, nil
}

func (f *FanoutOptions) transport() *http.Transport {
	transport := new(http.Transport)

	if f != nil {
		*transport = f.Transport
	}

	if transport.MaxIdleConnsPerHost < 1 {
		transport.MaxIdleConnsPerHost = DefaultMaxIdleConnsPerHost
	}

	return transport
}

func (f *FanoutOptions) fanoutTimeout() time.Duration {
	if f != nil && f.FanoutTimeout > 0 {
		return f.FanoutTimeout
	}

	return DefaultFanoutTimeout
}

func (f *FanoutOptions) clientTimeout() time.Duration {
	if f != nil && f.ClientTimeout > 0 {
		return f.ClientTimeout
	}

	return DefaultClientTimeout
}

func (f *FanoutOptions) maxClients() int64 {
	if f != nil && f.MaxClients > 0 {
		return f.MaxClients
	}

	return DefaultMaxClients
}

func (f *FanoutOptions) concurrency() int {
	if f != nil && f.Concurrency > 0 {
		return f.Concurrency
	}

	return DefaultConcurrency
}

func (f *FanoutOptions) encoderPoolSize() int {
	if f != nil && f.EncoderPoolSize > 0 {
		return f.EncoderPoolSize
	}

	return DefaultEncoderPoolSize
}

func (f *FanoutOptions) decoderPoolSize() int {
	if f != nil && f.DecoderPoolSize > 0 {
		return f.DecoderPoolSize
	}

	return DefaultDecoderPoolSize
}

func (f *FanoutOptions) middleware() []endpoint.Middleware {
	if f != nil {
		return f.Middleware
	}

	return nil
}

// NewEncoderPool creates a wrp.EncoderPool using this options, which can be nil to take defaults
func (o *FanoutOptions) NewEncoderPool(format wrp.Format) *wrp.EncoderPool {
	return wrp.NewEncoderPool(o.encoderPoolSize(), format)
}

// NewDecoderPool creates a wrp.DecoderPool using this options, which can be nil to take defaults
func (o *FanoutOptions) NewDecoderPool(format wrp.Format) *wrp.DecoderPool {
	return wrp.NewDecoderPool(o.decoderPoolSize(), format)
}

// NewFanoutEndpoint uses the supplied options to produce a go-kit HTTP server endpoint which
// fans out to the HTTP endpoints specified in the options.  The endpoint returned from this
// can be used to build one or more go-kit transport/http.Server objects.
//
// The FanoutOptions can be nil, in which case a set of defaults is used.
func NewFanoutEndpoint(o *FanoutOptions) (endpoint.Endpoint, error) {
	urls, err := o.urls()
	if err != nil {
		return nil, err
	}

	var (
		encoderPool = o.NewEncoderPool(wrp.Msgpack)
		decoderPool = o.NewDecoderPool(wrp.Msgpack)

		httpClient = &http.Client{
			Transport: o.transport(),
			Timeout:   o.clientTimeout(),
		}

		fanoutEndpoints = make(map[string]endpoint.Endpoint, len(urls))
		customHeader    = http.Header{
			"Accept": []string{"application/msgpack"},
		}
	)

	if authorization := o.authorization(); len(authorization) > 0 {
		customHeader.Set("Authorization", "Basic "+authorization)
	}

	for _, url := range urls {
		fanoutEndpoints[url.String()] =
			gokithttp.NewClient(
				o.method(),
				url,
				ClientEncodeRequestBody(encoderPool, customHeader),
				ClientDecodeResponseBody(decoderPool),
				gokithttp.SetClient(httpClient), gokithttp.ClientBefore(transporthttp.GetBody),
			).Endpoint()
	}

	var (
		middlewareChain = append(
			[]endpoint.Middleware{
				middleware.Logging,
				middleware.Busy(o.maxClients(), &xhttp.Error{Code: http.StatusServiceUnavailable, Text: "Server Busy"}),
				middleware.Timeout(o.fanoutTimeout()),
				middleware.Concurrent(o.concurrency(), &xhttp.Error{Code: http.StatusTooManyRequests, Text: "Too Many Requests"}),
			},
			o.middleware()...,
		)
	)

	return endpoint.Chain(
			middlewareChain[0],
			middlewareChain[1:]...,
		)(fanout.New(tracing.NewSpanner(), fanoutEndpoints)),
		nil
}
<commit_msg>Use a hardcoded xhttp.CheckRedirect<commit_after>package wrphttp

import (
	"net/http"
	"net/url"
	"time"

	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/middleware"
	"github.com/Comcast/webpa-common/middleware/fanout"
	"github.com/Comcast/webpa-common/tracing"
	"github.com/Comcast/webpa-common/transport/transporthttp"
	"github.com/Comcast/webpa-common/wrp"
	"github.com/Comcast/webpa-common/xhttp"
	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/log"
	gokithttp "github.com/go-kit/kit/transport/http"
)

const (
	DefaultMethod                            = "POST"
	DefaultEndpoint                          = "http://localhost:7000/api/v2/device/send"
	DefaultMaxIdleConnsPerHost               = 20
	DefaultFanoutTimeout       time.Duration = 45 * time.Second
	DefaultClientTimeout       time.Duration = 30 * time.Second
	DefaultMaxClients          int64         = 10000
	DefaultConcurrency                       = 1000
	DefaultEncoderPoolSize                   = 100
	DefaultDecoderPoolSize                   = 100
)

// FanoutOptions describe the options available for a go-kit HTTP server that does fanout via fanout.New.
type FanoutOptions struct {
	// Logger is the go-kit logger to use when creating the service fanout.  If not set, logging.DefaultLogger is used.
	Logger log.Logger `json:"-"`

	// Method is the HTTP method to use for all endpoints.  If not set, DefaultMethod is used.
	Method string `json:"method,omitempty"`

	// Endpoints are the URLs for each endpoint to fan out to.  If not set, DefaultEndpoint is used.
	Endpoints []string `json:"endpoints,omitempty"`

	// Authorization is the Basic Auth token.  There is no default for this field.
	Authorization string `json:"authorization"`

	// Transport is the http.Client transport
	Transport http.Transport `json:"transport"`

	// FanoutTimeout is the timeout for the entire fanout operation.  If not supplied, DefaultFanoutTimeout is used.
	FanoutTimeout time.Duration `json:"timeout"`

	// ClientTimeout is the http.Client Timeout.  If not set, DefaultClientTimeout is used.
	ClientTimeout time.Duration `json:"clientTimeout"`

	// MaxClients is the maximum number of concurrent clients that can be using the fanout.  This should be set to
	// something larger than the Concurrency field.
	MaxClients int64 `json:"maxClients"`

	// Concurrency is the maximum number of concurrent fanouts allowed.  This is enforced via a Concurrent middleware.
	// If this is not set, DefaultConcurrency is used.
	Concurrency int `json:"concurrency"`

	// EncoderPoolSize is the size of the WRP encoder pool.  If not set, DefaultEncoderPoolSize is used.
	EncoderPoolSize int

	// DecoderPoolSize is the size of the WRP encoder pool.  If not set, DefaultDecoderPoolSize is used.
	DecoderPoolSize int

	// Middleware is the extra Middleware to append, which can (and often is) empty
	Middleware []endpoint.Middleware `json:"-"`
}

func (f *FanoutOptions) logger() log.Logger {
	if f != nil && f.Logger != nil {
		return f.Logger
	}

	return logging.DefaultLogger()
}

func (f *FanoutOptions) method() string {
	if f != nil && len(f.Method) > 0 {
		return f.Method
	}

	return DefaultMethod
}

func (f *FanoutOptions) endpoints() []string {
	if f != nil && len(f.Endpoints) > 0 {
		return f.Endpoints
	}

	return []string{DefaultEndpoint}
}

func (f *FanoutOptions) authorization() string {
	if f != nil && len(f.Authorization) > 0 {
		return f.Authorization
	}

	return ""
}

func (f *FanoutOptions) urls() ([]*url.URL, error) {
	var urls []*url.URL
	for _, endpoint := range f.endpoints() {
		url, err := url.Parse(endpoint)
		if err != nil {
			return nil, err
		}

		urls = append(urls, url)
	}

	return urls, nil
}

func (f *FanoutOptions) transport() *http.Transport {
	transport := new(http.Transport)

	if f != nil {
		*transport = f.Transport
	}

	if transport.MaxIdleConnsPerHost < 1 {
		transport.MaxIdleConnsPerHost = DefaultMaxIdleConnsPerHost
	}

	return transport
}

func (f *FanoutOptions) fanoutTimeout() time.Duration {
	if f != nil && f.FanoutTimeout > 0 {
		return f.FanoutTimeout
	}

	return DefaultFanoutTimeout
}

func (f *FanoutOptions) clientTimeout() time.Duration {
	if f != nil && f.ClientTimeout > 0 {
		return f.ClientTimeout
	}

	return DefaultClientTimeout
}

func (f *FanoutOptions) maxClients() int64 {
	if f != nil && f.MaxClients > 0 {
		return f.MaxClients
	}

	return DefaultMaxClients
}

func (f *FanoutOptions) concurrency() int {
	if f != nil && f.Concurrency > 0 {
		return f.Concurrency
	}

	return DefaultConcurrency
}

func (f *FanoutOptions) encoderPoolSize() int {
	if f != nil && f.EncoderPoolSize > 0 {
		return f.EncoderPoolSize
	}

	return DefaultEncoderPoolSize
}

func (f *FanoutOptions) decoderPoolSize() int {
	if f != nil && f.DecoderPoolSize > 0 {
		return f.DecoderPoolSize
	}

	return DefaultDecoderPoolSize
}

func (f *FanoutOptions) middleware() []endpoint.Middleware {
	if f != nil {
		return f.Middleware
	}

	return nil
}

// NewEncoderPool creates a wrp.EncoderPool using this options, which can be nil to take defaults
func (o *FanoutOptions) NewEncoderPool(format wrp.Format) *wrp.EncoderPool {
	return wrp.NewEncoderPool(o.encoderPoolSize(), format)
}

// NewDecoderPool creates a wrp.DecoderPool using this options, which can be nil to take defaults
func (o *FanoutOptions) NewDecoderPool(format wrp.Format) *wrp.DecoderPool {
	return wrp.NewDecoderPool(o.decoderPoolSize(), format)
}

// NewFanoutEndpoint uses the supplied options to produce a go-kit HTTP server endpoint which
// fans out to the HTTP endpoints specified in the options.  The endpoint returned from this
// can be used to build one or more go-kit transport/http.Server objects.
//
// The FanoutOptions can be nil, in which case a set of defaults is used.
func NewFanoutEndpoint(o *FanoutOptions) (endpoint.Endpoint, error) {
	urls, err := o.urls()
	if err != nil {
		return nil, err
	}

	var (
		encoderPool = o.NewEncoderPool(wrp.Msgpack)
		decoderPool = o.NewDecoderPool(wrp.Msgpack)

		httpClient = &http.Client{
			CheckRedirect: xhttp.CheckRedirect(
				xhttp.RedirectPolicy{
					Logger: o.logger(),
				},
			),
			Transport: o.transport(),
			Timeout:   o.clientTimeout(),
		}

		fanoutEndpoints = make(map[string]endpoint.Endpoint, len(urls))
		customHeader    = http.Header{
			"Accept": []string{"application/msgpack"},
		}
	)

	if authorization := o.authorization(); len(authorization) > 0 {
		customHeader.Set("Authorization", "Basic "+authorization)
	}

	for _, url := range urls {
		fanoutEndpoints[url.String()] =
			gokithttp.NewClient(
				o.method(),
				url,
				ClientEncodeRequestBody(encoderPool, customHeader),
				ClientDecodeResponseBody(decoderPool),
				gokithttp.SetClient(httpClient), gokithttp.ClientBefore(transporthttp.GetBody),
			).Endpoint()
	}

	var (
		middlewareChain = append(
			[]endpoint.Middleware{
				middleware.Logging,
				middleware.Busy(o.maxClients(), &xhttp.Error{Code: http.StatusServiceUnavailable, Text: "Server Busy"}),
				middleware.Timeout(o.fanoutTimeout()),
				middleware.Concurrent(o.concurrency(), &xhttp.Error{Code: http.StatusTooManyRequests, Text: "Too Many Requests"}),
			},
			o.middleware()...,
		)
	)

	return endpoint.Chain(
			middlewareChain[0],
			middlewareChain[1:]...,
		)(fanout.New(tracing.NewSpanner(), fanoutEndpoints)),
		nil
}
<|endoftext|>


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

package main

import "os"    // this package contains features for basic I/O

func main() {
	os.Stdout.WriteString("Hello, world; or Καλημέρα κόσμε; or こんにちは 世界\n");
}
<commit_msg>restore "os" identifier to keep consistent with text.<commit_after>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import os "os"    // this package contains features for basic I/O

func main() {
	os.Stdout.WriteString("Hello, world; or Καλημέρα κόσμε; or こんにちは 世界\n");
}
<|endoftext|>


<commit_before>package relationships

import (
	"errors"
	"fmt"
	"github.com/bor3ham/reja/schema"
)

type ForeignKey struct {
	RelationshipStub
	Key        string
	ColumnName string
	Type       string
	Nullable   bool
	Default    func(schema.Context, interface{}) Pointer
}

func (fk ForeignKey) GetKey() string {
	return fk.Key
}
func (fk ForeignKey) GetType() string {
	return fk.Type
}

func (fk ForeignKey) GetSelectExtraColumns() []string {
	return []string{fk.ColumnName}
}
func (fk ForeignKey) GetSelectExtraVariables() []interface{} {
	var destination *string
	return []interface{}{
		&destination,
	}
}

func (fk ForeignKey) GetDefaultValue() interface{} {
	return schema.Result{}
}
func (fk ForeignKey) GetValues(
	c schema.Context,
	m *schema.Model,
	ids []string,
	extra [][]interface{},
) (
	map[string]interface{},
	map[string]map[string][]string,
) {
	values := map[string]interface{}{}
	maps := map[string]map[string][]string{}
	for index, result := range extra {
		myId := ids[index]

		// parse extra columns
		stringId, ok := result[0].(**string)
		if !ok {
			panic("Unable to convert extra fk id")
		}

		// check value does not already exist
		// a foreign key can only have one value
		_, exists := values[myId]
		if exists {
			existingValue, ok := values[myId].(Pointer)
			if !ok {
				panic("Unable to convert previous value")
			}
			if *stringId == nil {
				if existingValue.Data != nil {
					panic("Contradictory values in query results")
				}
			} else {
				if existingValue.Data == nil ||
					*existingValue.Data.ID != **stringId ||
					existingValue.Data.Type != fk.Type {
					panic("Contradictory values in query results")
				}
			}

			continue
		}

		selfLink := relationLink(c, m.Type, myId, fk.Key)
		newValue := schema.Result{
			Links: map[string]*string{
				"self": &selfLink,
			},
		}
		if *stringId != nil {
			newValue.Data = schema.InstancePointer{
				Type: fk.Type,
				ID:   *stringId,
			}
		}
		values[myId] = newValue

		// add to relation map
		if *stringId != nil {
			_, exists = maps[myId]
			if !exists {
				maps[myId] = map[string][]string{}
			}
			_, exists = maps[myId][fk.Type]
			if !exists {
				maps[myId][fk.Type] = []string{}
			}
			maps[myId][fk.Type] = append(maps[myId][fk.Type], **stringId)
		}
	}

	return values, maps
}

func (fk *ForeignKey) DefaultFallback(
	c schema.Context,
	val interface{},
	instance interface{},
) (
	interface{},
	error,
) {
	var fkVal Pointer
	if val == nil {
		fkVal = Pointer{Provided: false}
	} else {
		var err error
		fkVal, err = ParseResultPointer(val)
		if err != nil {
			return nil, err
		}
	}

	if !fkVal.Provided {
		if fk.Default != nil {
			return fk.Default(c, instance), nil
		}
		return nil, nil
	}
	return fkVal, nil
}
func (fk *ForeignKey) Validate(c schema.Context, val interface{}) (interface{}, error) {
	fkVal := AssertPointer(val)

	if fkVal.Data == nil {
		if !fk.Nullable {
			return nil, errors.New(fmt.Sprintf(
				"Relationship '%s' invalid: Cannot be null.",
				fk.Key,
			))
		}
		return fkVal, nil
	}

	valType := fkVal.Data.Type
	if fkVal.Data.ID == nil {
		return nil, errors.New(fmt.Sprintf(
			"Relationship '%s' invalid: Missing ID.",
			fk.Key,
		))
	}
	valID := *fkVal.Data.ID

	// validate the type is correct
	if valType != fk.Type {
		return nil, errors.New(fmt.Sprintf(
			"Relationship '%s' invalid: Incorrect type.",
			fk.Key,
		))
	}

	// check that the object exists
	model := c.GetServer().GetModel(fk.Type)
	include := schema.Include{
		Children: map[string]*schema.Include{},
	}
	instances, _, err := c.GetObjectsByIDs(model, []string{valID}, &include)
	if err != nil {
		panic(err)
	}
	if len(instances) == 0 {
		return nil, errors.New(fmt.Sprintf(
			"Relationship '%s' invalid: %s ID '%s' does not exist.",
			fk.Key,
			fk.Type,
			valID,
		))
	}
	return fkVal, nil
}
func (fk *ForeignKey) ValidateUpdate(
	c schema.Context,
	newVal interface{},
	oldVal interface{},
) (
	interface{},
	error,
) {
	// extract new value
	newPointer, err := ParseResultPointer(newVal)
	if err != nil {
		return nil, err
	}
	// if not provided, return nothing
	if !newPointer.Provided {
		return nil, nil
	}
	// clean and check validity of new value
	valid, err := fk.Validate(c, newPointer)
	if err != nil {
		return nil, err
	}
	validNewPointer := AssertPointer(valid)

	// extract old value
	oldResult, ok := oldVal.(schema.Result)
	if !ok {
		panic("Bad old foreign key value")
	}
	var oldValue Pointer
	if oldResult.Data == nil {
		oldValue = Pointer{}
	} else {
		oldPointer, ok := oldResult.Data.(schema.InstancePointer)
		if !ok {
			panic("Bad old foreign key value")
		}
		oldValue = Pointer{Data: &oldPointer}
	}

	if validNewPointer.Equal(oldValue) {
		return nil, nil
	}
	return validNewPointer, nil
}

func (fk *ForeignKey) GetInsertColumns(val interface{}) []string {
	return []string{
		fk.ColumnName,
	}
}
func (fk *ForeignKey) GetInsertValues(val interface{}) []interface{} {
	resultVal := AssertPointer(val)
	if resultVal.Data == nil {
		return []interface{}{
			nil,
		}
	}
	return []interface{}{
		resultVal.Data.ID,
	}
}

func AssertForeignKey(val interface{}) schema.Result {
	fkVal, ok := val.(schema.Result)
	if !ok {
		panic("Bad foreign key value")
	}
	return fkVal
}
<commit_msg>Tidy up foreign key comments<commit_after>package relationships

import (
	"errors"
	"fmt"
	"github.com/bor3ham/reja/schema"
)

type ForeignKey struct {
	RelationshipStub
	Key        string
	ColumnName string
	Type       string
	Nullable   bool
	Default    func(schema.Context, interface{}) Pointer
}

func (fk ForeignKey) GetKey() string {
	return fk.Key
}
func (fk ForeignKey) GetType() string {
	return fk.Type
}

func (fk ForeignKey) GetSelectExtraColumns() []string {
	return []string{fk.ColumnName}
}
func (fk ForeignKey) GetSelectExtraVariables() []interface{} {
	var destination *string
	return []interface{}{
		&destination,
	}
}

func (fk ForeignKey) GetDefaultValue() interface{} {
	return schema.Result{}
}
func (fk ForeignKey) GetValues(
	c schema.Context,
	m *schema.Model,
	ids []string,
	extra [][]interface{},
) (
	map[string]interface{},
	map[string]map[string][]string,
) {
	values := map[string]interface{}{}
	maps := map[string]map[string][]string{}
	for index, result := range extra {
		myId := ids[index]

		// parse extra columns
		stringId, ok := result[0].(**string)
		if !ok {
			panic("Unable to convert extra fk id")
		}

		// check value does not already exist
		// a foreign key can only have one value
		_, exists := values[myId]
		if exists {
			existingValue, ok := values[myId].(Pointer)
			if !ok {
				panic("Unable to convert previous value")
			}
			if *stringId == nil {
				if existingValue.Data != nil {
					panic("Contradictory values in query results")
				}
			} else {
				if existingValue.Data == nil ||
					*existingValue.Data.ID != **stringId ||
					existingValue.Data.Type != fk.Type {
					panic("Contradictory values in query results")
				}
			}

			continue
		}

		selfLink := relationLink(c, m.Type, myId, fk.Key)
		newValue := schema.Result{
			Links: map[string]*string{
				"self": &selfLink,
			},
		}
		if *stringId != nil {
			newValue.Data = schema.InstancePointer{
				Type: fk.Type,
				ID:   *stringId,
			}
		}
		values[myId] = newValue

		// add to relation map
		if *stringId != nil {
			_, exists = maps[myId]
			if !exists {
				maps[myId] = map[string][]string{}
			}
			_, exists = maps[myId][fk.Type]
			if !exists {
				maps[myId][fk.Type] = []string{}
			}
			maps[myId][fk.Type] = append(maps[myId][fk.Type], **stringId)
		}
	}

	return values, maps
}

func (fk *ForeignKey) DefaultFallback(
	c schema.Context,
	val interface{},
	instance interface{},
) (
	interface{},
	error,
) {
	var fkVal Pointer
	if val == nil {
		fkVal = Pointer{Provided: false}
	} else {
		var err error
		fkVal, err = ParseResultPointer(val)
		if err != nil {
			return nil, err
		}
	}

	if !fkVal.Provided {
		if fk.Default != nil {
			return fk.Default(c, instance), nil
		}
		return nil, nil
	}
	return fkVal, nil
}
func (fk *ForeignKey) Validate(c schema.Context, val interface{}) (interface{}, error) {
	fkVal := AssertPointer(val)

	if fkVal.Data == nil {
		if !fk.Nullable {
			return nil, errors.New(fmt.Sprintf(
				"Relationship '%s' invalid: Cannot be null.",
				fk.Key,
			))
		}
		return fkVal, nil
	}

	valType := fkVal.Data.Type
	if fkVal.Data.ID == nil {
		return nil, errors.New(fmt.Sprintf(
			"Relationship '%s' invalid: Missing ID.",
			fk.Key,
		))
	}
	valID := *fkVal.Data.ID

	// validate the type is correct
	if valType != fk.Type {
		return nil, errors.New(fmt.Sprintf(
			"Relationship '%s' invalid: Incorrect type.",
			fk.Key,
		))
	}

	// check that the object exists
	model := c.GetServer().GetModel(fk.Type)
	include := schema.Include{
		Children: map[string]*schema.Include{},
	}
	instances, _, err := c.GetObjectsByIDs(model, []string{valID}, &include)
	if err != nil {
		panic(err)
	}
	if len(instances) == 0 {
		return nil, errors.New(fmt.Sprintf(
			"Relationship '%s' invalid: %s ID '%s' does not exist.",
			fk.Key,
			fk.Type,
			valID,
		))
	}
	return fkVal, nil
}
func (fk *ForeignKey) ValidateUpdate(
	c schema.Context,
	newVal interface{},
	oldVal interface{},
) (
	interface{},
	error,
) {
	// extract new value
	newPointer, err := ParseResultPointer(newVal)
	if err != nil {
		return nil, err
	}
	// if not provided, return nothing
	if !newPointer.Provided {
		return nil, nil
	}
	// clean and check validity of new value
	valid, err := fk.Validate(c, newPointer)
	if err != nil {
		return nil, err
	}
	validNewPointer := AssertPointer(valid)

	// extract old value
	oldResult, ok := oldVal.(schema.Result)
	if !ok {
		panic("Bad old foreign key value")
	}
	var oldValue Pointer
	if oldResult.Data == nil {
		oldValue = Pointer{}
	} else {
		oldPointer, ok := oldResult.Data.(schema.InstancePointer)
		if !ok {
			panic("Bad old foreign key value")
		}
		oldValue = Pointer{Data: &oldPointer}
	}

	// return nothing if no changes
	if validNewPointer.Equal(oldValue) {
		return nil, nil
	}
	// otherwise return new validated value
	return validNewPointer, nil
}

func (fk *ForeignKey) GetInsertColumns(val interface{}) []string {
	return []string{
		fk.ColumnName,
	}
}
func (fk *ForeignKey) GetInsertValues(val interface{}) []interface{} {
	resultVal := AssertPointer(val)
	if resultVal.Data == nil {
		return []interface{}{
			nil,
		}
	}
	return []interface{}{
		resultVal.Data.ID,
	}
}

func AssertForeignKey(val interface{}) schema.Result {
	fkVal, ok := val.(schema.Result)
	if !ok {
		panic("Bad foreign key value")
	}
	return fkVal
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"reflect"
	"testing"
)

type SusiTest struct {
	students map[int][]byte
	courses  map[string][]byte
}

func newSusiTest() (*SusiTest, *Susi) {
	st := new(SusiTest)
	st.students = map[int][]byte{
		11111: []byte(`{"faculty_number":11111,"first_name":"Test","last_name":"One","master":false,"academic_year":1}`),
		22222: []byte(`{"faculty_number":22222,"first_name":"Test","last_name":"Two","master":false,"academic_year":2}`),
		33333: []byte(`{"faculty_number":33333,"first_name":"Test","last_name":"Three","master":false,"academic_year":3}`),
		44444: []byte(`{"faculty_number":44444,"first_name":"Test","last_name":"Four","master":false,"academic_year":4}`),
		55555: []byte(`{"faculty_number":55555,"first_name":"Test","last_name":"Master","master":true,"academic_year":0}`),
	}

	st.courses = map[string][]byte{
		"AR":   []byte(`{"course_name":"Advanced Robotics","course_identifier":"AR","minimum_academic_year":3,"masters_only":false,"available_places":2}`),
		"R101": []byte(`{"course_name":"Robotics 101","course_identifier":"R101","minimum_academic_year":1,"masters_only":false,"available_places":2}`),
		"MO":   []byte(`{"course_name":"Masters Only","course_identifier":"MO","minimum_academic_year":0,"masters_only":true,"available_places":2}`),
		"FC":   []byte(`{"course_name":"Full Course","course_identifier":"FC","minimum_academic_year":0,"masters_only":false,"available_places":0}`),
	}

	return st, NewSusi()
}

func (st *SusiTest) AddStudents(s *Susi, fns ...int) error {
	for _, fn := range fns {
		err := s.AddStudent(st.students[fn])
		if err != nil {
			return err
		}
	}
	return nil
}

func (st *SusiTest) AddCourses(s *Susi, identifiers ...string) error {
	for _, identifier := range identifiers {
		err := s.AddCourse(st.courses[identifier])
		if err != nil {
			return err
		}
	}
	return nil
}

func (st *SusiTest) Enroll(s *Susi, fn int, identifier string) error {
	payload := []byte(fmt.Sprintf("{\"faculty_number\":%d,\"course_identifier\":\"%s\"}", fn, identifier))
	return s.Enroll(payload)
}

// Errors
func (st *SusiTest) studentCannotEnrollError(student *Student, course *Course) string {
	return fmt.Sprintf("%s %s не покрива изискванията за %s!", student.FirstName, student.LastName, course.CourseName)
}

func (st *SusiTest) studentNotFoundError(fn int) string {
	return fmt.Sprintf("Няма студент с факултетен номер %d!", fn)
}

func (st *SusiTest) studentAlreadyExistsError(fn int) string {
	return fmt.Sprintf("Студент с факултетен номер %d вече съществува!", fn)
}

func (st *SusiTest) courseNotFoundError(identifier string) string {
	return fmt.Sprintf("Няма курс с identifier - %s!", identifier)
}

func (st *SusiTest) courseAlreadyExistsError(identifier string) string {
	return fmt.Sprintf("Курс с identifier %s вече съществува!", identifier)
}

func (st *SusiTest) courseIsFullError(identifier string) string {
	return fmt.Sprint("Няма свободни места за курс с identifier - %s!", identifier)
}

func (st *SusiTest) enrollmentAlreadyExistsError(fn int, identifier string) string {
	return fmt.Sprintf("Студент с факултетен номер %d е вече записан за курс с identifier %s!", fn, identifier)
}

func (st *SusiTest) enrollmentNotFoundError(fn int, identifier string) string {
	return fmt.Sprintf("Студент с факултетен номер %d не е записан за курса с identifier %s!", fn, identifier)
}

// Tests

func TestAddStudent(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}
}

func TestFindMissingStudent(t *testing.T) {
	st, s := newSusiTest()
	_, err := s.FindStudent(22222)

	if err == nil {
		t.Error("Expected to recieve an error when getting an missing student!")
	}

	got := err.Error()
	expected := st.studentNotFoundError(22222)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestAddCourse(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddCourses(s, "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}
}

func TestEnroll(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 22222)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "AR", "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll in a course, recieved: %s", err.Error())
	}

	err = st.Enroll(s, 22222, "R101")
	if err != nil {
		t.Errorf("Failed to enroll in a course, recieved: %s", err.Error())
	}
}

func TestEnrollMoreThanAvailablePlaces(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 22222, 33333)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the first student, got: %s", err.Error())
	}

	err = st.Enroll(s, 22222, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the second student, got: %s", err.Error())
	}

	err = st.Enroll(s, 33333, "R101")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling the third student!")
	}

	got := err.Error()
	expected := st.courseIsFullError("R101")
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollTwiceInTheSameCourseWithTheSameUser(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the first time, got: %s", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling twise in the same course with the same user!")
	}

	got := err.Error()
	expected := st.enrollmentAlreadyExistsError(11111, "R101")
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollWhenTheRequirementsAreNotMet(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "AR")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling in a course where the student doesn't meet the requirements!")
	}

	student, _ := s.FindStudent(11111)
	course, _ := s.FindCourse("AR")

	got := err.Error()
	expected := st.studentCannotEnrollError(student, course)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollInMasterOnlyCourse(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 55555)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "MO", "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 55555, "MO")
	if err != nil {
		t.Errorf("Failed to enroll in a master only course when the student is a master, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "MO")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling in a master only course where the student is not a master!")
	}

	student, _ := s.FindStudent(11111)
	course, _ := s.FindCourse("MO")

	got := err.Error()
	expected := st.studentCannotEnrollError(student, course)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestStudentImplementStringer(t *testing.T) {
	st, s := newSusiTest()
	_ = st.AddStudents(s, 11111, 22222)
	student, _ := s.FindStudent(11111)

	if reflect.TypeOf(student).Elem().Implements(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) {
		t.Error("Student doesn't implement Stringer!")
	}

	got := student.String()
	expected := "11111 Test One"
	if got != expected {
		t.Errorf("Student#String failed! Expected: %s, got: %s", expected, got)
	}
}

func TestCourseImplementStringer(t *testing.T) {
	st, s := newSusiTest()
	_ = st.AddCourses(s, "AR", "R101")
	course, _ := s.FindCourse("AR")

	if reflect.TypeOf(course).Elem().Implements(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) {
		t.Error("Course doesn't implement Stringer!")
	}

	got := course.String()
	expected := "AR Advanced Robotics"
	if got != expected {
		t.Errorf("Course#String failed! Expected: %s, got: %s", expected, got)
	}
}
<commit_msg>FIx example tests.<commit_after>package main

import (
	"fmt"
	"reflect"
	"testing"
)

type SusiTest struct {
	students map[int][]byte
	courses  map[string][]byte
}

func newSusiTest() (*SusiTest, *Susi) {
	st := new(SusiTest)
	st.students = map[int][]byte{
		11111: []byte(`{"faculty_number":11111,"first_name":"Test","last_name":"One","master":false,"academic_year":1}`),
		22222: []byte(`{"faculty_number":22222,"first_name":"Test","last_name":"Two","master":false,"academic_year":2}`),
		33333: []byte(`{"faculty_number":33333,"first_name":"Test","last_name":"Three","master":false,"academic_year":3}`),
		44444: []byte(`{"faculty_number":44444,"first_name":"Test","last_name":"Four","master":false,"academic_year":4}`),
		55555: []byte(`{"faculty_number":55555,"first_name":"Test","last_name":"Master","master":true,"academic_year":0}`),
	}

	st.courses = map[string][]byte{
		"AR":   []byte(`{"course_name":"Advanced Robotics","course_identifier":"AR","minimum_academic_year":3,"masters_only":false,"available_places":2}`),
		"R101": []byte(`{"course_name":"Robotics 101","course_identifier":"R101","minimum_academic_year":1,"masters_only":false,"available_places":2}`),
		"MO":   []byte(`{"course_name":"Masters Only","course_identifier":"MO","minimum_academic_year":0,"masters_only":true,"available_places":2}`),
		"FC":   []byte(`{"course_name":"Full Course","course_identifier":"FC","minimum_academic_year":0,"masters_only":false,"available_places":0}`),
	}

	return st, NewSusi()
}

func (st *SusiTest) AddStudents(s *Susi, fns ...int) error {
	for _, fn := range fns {
		err := s.AddStudent(st.students[fn])
		if err != nil {
			return err
		}
	}
	return nil
}

func (st *SusiTest) AddCourses(s *Susi, identifiers ...string) error {
	for _, identifier := range identifiers {
		err := s.AddCourse(st.courses[identifier])
		if err != nil {
			return err
		}
	}
	return nil
}

func (st *SusiTest) Enroll(s *Susi, fn int, identifier string) error {
	payload := []byte(fmt.Sprintf("{\"faculty_number\":%d,\"course_identifier\":\"%s\"}", fn, identifier))
	return s.Enroll(payload)
}

// Errors
func (st *SusiTest) studentCannotEnrollError(student *Student, course *Course) string {
	return fmt.Sprintf("%s %s не покрива изискванията за %s!", student.FirstName, student.LastName, course.CourseName)
}

func (st *SusiTest) studentNotFoundError(fn int) string {
	return fmt.Sprintf("Няма студент с факултетен номер %d!", fn)
}

func (st *SusiTest) studentAlreadyExistsError(fn int) string {
	return fmt.Sprintf("Студент с факултетен номер %d вече съществува!", fn)
}

func (st *SusiTest) courseNotFoundError(identifier string) string {
	return fmt.Sprintf("Няма курс с identifier - %s!", identifier)
}

func (st *SusiTest) courseAlreadyExistsError(identifier string) string {
	return fmt.Sprintf("Курс с identifier %s вече съществува!", identifier)
}

func (st *SusiTest) courseIsFullError(identifier string) string {
	return fmt.Sprintf("Няма свободни места за курс с identifier - %s!", identifier)
}

func (st *SusiTest) enrollmentAlreadyExistsError(fn int, identifier string) string {
	return fmt.Sprintf("Студент с факултетен номер %d е вече записан за курс с identifier %s!", fn, identifier)
}

func (st *SusiTest) enrollmentNotFoundError(fn int, identifier string) string {
	return fmt.Sprintf("Студент с факултетен номер %d не е записан за курса с identifier %s!", fn, identifier)
}

// Tests

func TestAddStudent(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}
}

func TestFindMissingStudent(t *testing.T) {
	st, s := newSusiTest()
	_, err := s.FindStudent(22222)

	if err == nil {
		t.Error("Expected to recieve an error when getting an missing student!")
	}

	got := err.Error()
	expected := st.studentNotFoundError(22222)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestAddCourse(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddCourses(s, "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}
}

func TestEnroll(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 22222)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "AR", "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll in a course, recieved: %s", err.Error())
	}

	err = st.Enroll(s, 22222, "R101")
	if err != nil {
		t.Errorf("Failed to enroll in a course, recieved: %s", err.Error())
	}
}

func TestEnrollMoreThanAvailablePlaces(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 22222, 33333)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the first student, got: %s", err.Error())
	}

	err = st.Enroll(s, 22222, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the second student, got: %s", err.Error())
	}

	err = st.Enroll(s, 33333, "R101")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling the third student!")
	}

	got := err.Error()
	expected := st.courseIsFullError("R101")
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollTwiceInTheSameCourseWithTheSameUser(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the first time, got: %s", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling twise in the same course with the same user!")
	}

	got := err.Error()
	expected := st.enrollmentAlreadyExistsError(11111, "R101")
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollWhenTheRequirementsAreNotMet(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "AR")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling in a course where the student doesn't meet the requirements!")
	}

	student, _ := s.FindStudent(11111)
	course, _ := s.FindCourse("AR")

	got := err.Error()
	expected := st.studentCannotEnrollError(student, course)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollInMasterOnlyCourse(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 55555)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "MO", "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 55555, "MO")
	if err != nil {
		t.Errorf("Failed to enroll in a master only course when the student is a master, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "MO")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling in a master only course where the student is not a master!")
	}

	student, _ := s.FindStudent(11111)
	course, _ := s.FindCourse("MO")

	got := err.Error()
	expected := st.studentCannotEnrollError(student, course)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestStudentImplementStringer(t *testing.T) {
	st, s := newSusiTest()
	_ = st.AddStudents(s, 11111, 22222)
	student, _ := s.FindStudent(11111)

	if reflect.TypeOf(student).Elem().Implements(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) {
		t.Error("Student doesn't implement Stringer!")
	}

	got := student.String()
	expected := "11111 Test One"
	if got != expected {
		t.Errorf("Student#String failed! Expected: %s, got: %s", expected, got)
	}
}

func TestCourseImplementStringer(t *testing.T) {
	st, s := newSusiTest()
	_ = st.AddCourses(s, "AR", "R101")
	course, _ := s.FindCourse("AR")

	if reflect.TypeOf(course).Elem().Implements(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) {
		t.Error("Course doesn't implement Stringer!")
	}

	got := course.String()
	expected := "AR Advanced Robotics"
	if got != expected {
		t.Errorf("Course#String failed! Expected: %s, got: %s", expected, got)
	}
}
<|endoftext|>


<commit_before>/*
 * config.go
 *
 * Copyright 2017 Bill Zissimopoulos
 */
/*
 * This file is part of netchan.
 *
 * It is licensed under the MIT license. The full license text can be found
 * in the License.txt file at the root of this project.
 */

package netchan

import (
	"time"
)

const (
	configMaxMsgSize = 16 * 1024 * 1024
	configMaxLinks   = 4
)

type Config struct {
	MaxLinks      int
	RedialTimeout time.Duration
	IdleTimeout   time.Duration
}

func (self *Config) Clone() *Config {
	clone := *self
	return &clone
}
<commit_msg>Config: add documentation<commit_after>/*
 * config.go
 *
 * Copyright 2017 Bill Zissimopoulos
 */
/*
 * This file is part of netchan.
 *
 * It is licensed under the MIT license. The full license text can be found
 * in the License.txt file at the root of this project.
 */

package netchan

import (
	"time"
)

const (
	configMaxMsgSize = 16 * 1024 * 1024
	configMaxLinks   = 4
)

// Config contains configuration parameters for a Transport.
type Config struct {
	// MaxLinks contains the maximum number of links that may be opened
	// to a particular address/URI.
	MaxLinks int

	// RedialTimeout contains a timeout for "redial" attempts. If it is
	// non-zero a Transport will retry dialing if a dialing error occurs
	// for at least the duration specified in RedialTimeout. If this
	// field is zero no redial attempts will be made.
	RedialTimeout time.Duration

	// IdleTimeout will close connections that have been idle for the
	// specified duration. If this field is zero idle connections will
	// not be closed.
	IdleTimeout time.Duration
}

// Clone makes a shallow clone of the receiver Config.
func (self *Config) Clone() *Config {
	clone := *self
	return &clone
}
<|endoftext|>