<commit_before>/*
Copyright 2020 The cert-manager Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package framework

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

	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
	apiextensionsinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
	"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/rest"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/envtest"

	webhooktesting "github.com/jetstack/cert-manager/cmd/webhook/app/testing"
	"github.com/jetstack/cert-manager/internal/test/paths"
	"github.com/jetstack/cert-manager/internal/webhook"
	"github.com/jetstack/cert-manager/pkg/api"
	"github.com/jetstack/cert-manager/pkg/webhook/handlers"
	"github.com/jetstack/cert-manager/test/internal/apiserver"
)

type StopFunc func()

// controlPlaneOptions has parameters for the control plane of the integration
// test framework which can be overridden in tests.
type controlPlaneOptions struct {
	crdsDir                  *string
	webhookConversionHandler handlers.ConversionHook
}

type RunControlPlaneOption func(*controlPlaneOptions)

// WithCRDDirectory allows alternative CRDs to be loaded into the test API
// server in tests.
func WithCRDDirectory(directory string) RunControlPlaneOption {
	return func(o *controlPlaneOptions) {
		o.crdsDir = pointer.StringPtr(directory)
	}
}

// WithWebhookConversionHandler allows the webhook handler for the `/convert`
// endpoint to be overridden in tests.
func WithWebhookConversionHandler(handler handlers.ConversionHook) RunControlPlaneOption {
	return func(o *controlPlaneOptions) {
		o.webhookConversionHandler = handler
	}
}

func RunControlPlane(t *testing.T, ctx context.Context, optionFunctions ...RunControlPlaneOption) (*rest.Config, StopFunc) {
	options := &controlPlaneOptions{
		crdsDir: pointer.StringPtr(paths.CRDDirectory(t)),
	}

	for _, f := range optionFunctions {
		f(options)
	}

	env, stopControlPlane := apiserver.RunBareControlPlane(t)
	config := env.Config

	webhookOpts, stopWebhook := webhooktesting.StartWebhookServer(
		t, ctx, []string{"--api-server-host=" + config.Host},
		webhook.WithConversionHandler(options.webhookConversionHandler),
	)

	crds := readCustomResourcesAtPath(t, *options.crdsDir)
	for _, crd := range crds {
		t.Logf("Found CRD with name %q", crd.Name)
	}
	patchCRDConversion(crds, webhookOpts.URL, webhookOpts.CAPEM)

	if _, err := envtest.InstallCRDs(config, envtest.CRDInstallOptions{
		CRDs: crds,
	}); err != nil {
		t.Fatal(err)
	}

	cl, err := client.New(config, client.Options{Scheme: api.Scheme})
	if err != nil {
		t.Fatal(err)
	}

	// installing the validating webhooks, not using WebhookInstallOptions as it patches the CA to be it's own
	err = cl.Create(ctx, getValidatingWebhookConfig(webhookOpts.URL, webhookOpts.CAPEM))
	if err != nil {
		t.Fatal(err)
	}

	// installing the mutating webhooks, not using WebhookInstallOptions as it patches the CA to be it's own
	err = cl.Create(ctx, getMutatingWebhookConfig(webhookOpts.URL, webhookOpts.CAPEM))
	if err != nil {
		t.Fatal(err)
	}

	return config, func() {
		defer stopWebhook()
		stopControlPlane()
	}
}

var (
	internalScheme = runtime.NewScheme()
)

func init() {
	utilruntime.Must(metav1.AddMetaToScheme(internalScheme))
	apiextensionsinstall.Install(internalScheme)
}

// patchCRDConversion overrides the conversion configuration of the CRDs that
// are loaded in to the integration test API server,
// configuring the conversion to be handled by the local webhook server.
func patchCRDConversion(crds []*apiextensionsv1.CustomResourceDefinition, url string, caPEM []byte) {
	for i := range crds {
		url := fmt.Sprintf("%s%s", url, "/convert")
		crds[i].Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
			Strategy: apiextensionsv1.WebhookConverter,
			Webhook: &apiextensionsv1.WebhookConversion{
				ClientConfig: &apiextensionsv1.WebhookClientConfig{
					URL:      &url,
					CABundle: caPEM,
				},
				ConversionReviewVersions: []string{"v1"},
			},
		}
	}
}

func readCustomResourcesAtPath(t *testing.T, path string) []*apiextensionsv1.CustomResourceDefinition {
	serializer := jsonserializer.NewSerializerWithOptions(jsonserializer.DefaultMetaFactory, internalScheme, internalScheme, jsonserializer.SerializerOptions{
		Yaml: true,
	})
	converter := runtime.UnsafeObjectConvertor(internalScheme)
	codec := versioning.NewCodec(serializer, serializer, converter, internalScheme, internalScheme, internalScheme, runtime.InternalGroupVersioner, runtime.InternalGroupVersioner, internalScheme.Name())

	var crds []*apiextensionsv1.CustomResourceDefinition
	if err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if filepath.Ext(path) != ".yaml" {
			return nil
		}
		crd, err := readCRDsAtPath(codec, converter, path)
		if err != nil {
			return fmt.Errorf("failed reading CRDs at path %s: %w", path, err)
		}
		crds = append(crds, crd...)
		return nil
	}); err != nil {
		t.Fatal(err)
	}
	return crds
}

func readCRDsAtPath(codec runtime.Codec, converter runtime.ObjectConvertor, path string) ([]*apiextensionsv1.CustomResourceDefinition, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	var crds []*apiextensionsv1.CustomResourceDefinition
	for _, d := range strings.Split(string(data), "\n---\n") {
		// skip empty YAML documents
		if strings.TrimSpace(d) == "" {
			continue
		}

		internalCRD := &apiextensions.CustomResourceDefinition{}
		if _, _, err := codec.Decode([]byte(d), nil, internalCRD); err != nil {
			return nil, err
		}

		out := apiextensionsv1.CustomResourceDefinition{}
		if err := converter.Convert(internalCRD, &out, nil); err != nil {
			return nil, err
		}

		crds = append(crds, &out)
	}

	return crds, nil
}

func getValidatingWebhookConfig(url string, caPEM []byte) client.Object {
	failurePolicy := admissionregistrationv1.Fail
	sideEffects := admissionregistrationv1.SideEffectClassNone
	validateURL := fmt.Sprintf("%s/validate", url)
	webhook := admissionregistrationv1.ValidatingWebhookConfiguration{
		ObjectMeta: metav1.ObjectMeta{
			Name: "cert-manager-webhook",
		},
		Webhooks: []admissionregistrationv1.ValidatingWebhook{
			{
				Name: "webhook.cert-manager.io",
				ClientConfig: admissionregistrationv1.WebhookClientConfig{
					URL:      &validateURL,
					CABundle: caPEM,
				},
				Rules: []admissionregistrationv1.RuleWithOperations{
					{
						Operations: []admissionregistrationv1.OperationType{
							admissionregistrationv1.Create,
							admissionregistrationv1.Update,
						},
						Rule: admissionregistrationv1.Rule{
							APIGroups:   []string{"cert-manager.io", "acme.cert-manager.io"},
							APIVersions: []string{"*"},
							Resources:   []string{"*/*"},
						},
					},
				},
				FailurePolicy:           &failurePolicy,
				SideEffects:             &sideEffects,
				AdmissionReviewVersions: []string{"v1"},
			},
		},
	}

	return &webhook
}

func getMutatingWebhookConfig(url string, caPEM []byte) client.Object {
	failurePolicy := admissionregistrationv1.Fail
	sideEffects := admissionregistrationv1.SideEffectClassNone
	validateURL := fmt.Sprintf("%s/mutate", url)
	webhook := admissionregistrationv1.MutatingWebhookConfiguration{
		ObjectMeta: metav1.ObjectMeta{
			Name: "cert-manager-webhook",
		},
		Webhooks: []admissionregistrationv1.MutatingWebhook{
			{
				Name: "webhook.cert-manager.io",
				ClientConfig: admissionregistrationv1.WebhookClientConfig{
					URL:      &validateURL,
					CABundle: caPEM,
				},
				Rules: []admissionregistrationv1.RuleWithOperations{
					{
						Operations: []admissionregistrationv1.OperationType{
							admissionregistrationv1.Create,
							admissionregistrationv1.Update,
						},
						Rule: admissionregistrationv1.Rule{
							APIGroups:   []string{"cert-manager.io", "acme.cert-manager.io"},
							APIVersions: []string{"*"},
							Resources:   []string{"*/*"},
						},
					},
				},
				FailurePolicy:           &failurePolicy,
				SideEffects:             &sideEffects,
				AdmissionReviewVersions: []string{"v1"},
			},
		},
	}

	return &webhook
}
<commit_msg>Fix webhook kubernetes config in integration tests<commit_after>/*
Copyright 2020 The cert-manager Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package framework

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
	apiextensionsinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json"
	"k8s.io/apimachinery/pkg/runtime/serializer/versioning"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/rest"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/envtest"

	webhooktesting "github.com/jetstack/cert-manager/cmd/webhook/app/testing"
	"github.com/jetstack/cert-manager/internal/test/paths"
	"github.com/jetstack/cert-manager/internal/webhook"
	"github.com/jetstack/cert-manager/pkg/api"
	"github.com/jetstack/cert-manager/pkg/webhook/handlers"
	"github.com/jetstack/cert-manager/test/internal/apiserver"
)

type StopFunc func()

// controlPlaneOptions has parameters for the control plane of the integration
// test framework which can be overridden in tests.
type controlPlaneOptions struct {
	crdsDir                  *string
	webhookConversionHandler handlers.ConversionHook
}

type RunControlPlaneOption func(*controlPlaneOptions)

// WithCRDDirectory allows alternative CRDs to be loaded into the test API
// server in tests.
func WithCRDDirectory(directory string) RunControlPlaneOption {
	return func(o *controlPlaneOptions) {
		o.crdsDir = pointer.StringPtr(directory)
	}
}

// WithWebhookConversionHandler allows the webhook handler for the `/convert`
// endpoint to be overridden in tests.
func WithWebhookConversionHandler(handler handlers.ConversionHook) RunControlPlaneOption {
	return func(o *controlPlaneOptions) {
		o.webhookConversionHandler = handler
	}
}

func RunControlPlane(t *testing.T, ctx context.Context, optionFunctions ...RunControlPlaneOption) (*rest.Config, StopFunc) {
	options := &controlPlaneOptions{
		crdsDir: pointer.StringPtr(paths.CRDDirectory(t)),
	}

	for _, f := range optionFunctions {
		f(options)
	}

	env, stopControlPlane := apiserver.RunBareControlPlane(t)
	testuser, err := env.ControlPlane.AddUser(envtest.User{Name: "test-user", Groups: []string{"cluster-admin"}}, env.Config)
	if err != nil {
		t.Fatal(err)
	}

	kubeconfig, err := testuser.KubeConfig()
	if err != nil {
		t.Fatal(err)
	}

	f, err := ioutil.TempFile("", "integration-")
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()
	defer func() {
		os.Remove(f.Name())
	}()
	if _, err := f.Write(kubeconfig); err != nil {
		t.Fatal(err)
	}

	webhookOpts, stopWebhook := webhooktesting.StartWebhookServer(
		t, ctx, []string{"--kubeconfig", f.Name()},
		webhook.WithConversionHandler(options.webhookConversionHandler),
	)

	crds := readCustomResourcesAtPath(t, *options.crdsDir)
	for _, crd := range crds {
		t.Logf("Found CRD with name %q", crd.Name)
	}
	patchCRDConversion(crds, webhookOpts.URL, webhookOpts.CAPEM)

	if _, err := envtest.InstallCRDs(env.Config, envtest.CRDInstallOptions{
		CRDs: crds,
	}); err != nil {
		t.Fatal(err)
	}

	cl, err := client.New(env.Config, client.Options{Scheme: api.Scheme})
	if err != nil {
		t.Fatal(err)
	}

	// installing the validating webhooks, not using WebhookInstallOptions as it patches the CA to be it's own
	err = cl.Create(ctx, getValidatingWebhookConfig(webhookOpts.URL, webhookOpts.CAPEM))
	if err != nil {
		t.Fatal(err)
	}

	// installing the mutating webhooks, not using WebhookInstallOptions as it patches the CA to be it's own
	err = cl.Create(ctx, getMutatingWebhookConfig(webhookOpts.URL, webhookOpts.CAPEM))
	if err != nil {
		t.Fatal(err)
	}

	return env.Config, func() {
		defer stopWebhook()
		stopControlPlane()
	}
}

var (
	internalScheme = runtime.NewScheme()
)

func init() {
	utilruntime.Must(metav1.AddMetaToScheme(internalScheme))
	apiextensionsinstall.Install(internalScheme)
}

// patchCRDConversion overrides the conversion configuration of the CRDs that
// are loaded in to the integration test API server,
// configuring the conversion to be handled by the local webhook server.
func patchCRDConversion(crds []*apiextensionsv1.CustomResourceDefinition, url string, caPEM []byte) {
	for i := range crds {
		url := fmt.Sprintf("%s%s", url, "/convert")
		crds[i].Spec.Conversion = &apiextensionsv1.CustomResourceConversion{
			Strategy: apiextensionsv1.WebhookConverter,
			Webhook: &apiextensionsv1.WebhookConversion{
				ClientConfig: &apiextensionsv1.WebhookClientConfig{
					URL:      &url,
					CABundle: caPEM,
				},
				ConversionReviewVersions: []string{"v1"},
			},
		}
	}
}

func readCustomResourcesAtPath(t *testing.T, path string) []*apiextensionsv1.CustomResourceDefinition {
	serializer := jsonserializer.NewSerializerWithOptions(jsonserializer.DefaultMetaFactory, internalScheme, internalScheme, jsonserializer.SerializerOptions{
		Yaml: true,
	})
	converter := runtime.UnsafeObjectConvertor(internalScheme)
	codec := versioning.NewCodec(serializer, serializer, converter, internalScheme, internalScheme, internalScheme, runtime.InternalGroupVersioner, runtime.InternalGroupVersioner, internalScheme.Name())

	var crds []*apiextensionsv1.CustomResourceDefinition
	if err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if filepath.Ext(path) != ".yaml" {
			return nil
		}
		crd, err := readCRDsAtPath(codec, converter, path)
		if err != nil {
			return fmt.Errorf("failed reading CRDs at path %s: %w", path, err)
		}
		crds = append(crds, crd...)
		return nil
	}); err != nil {
		t.Fatal(err)
	}
	return crds
}

func readCRDsAtPath(codec runtime.Codec, converter runtime.ObjectConvertor, path string) ([]*apiextensionsv1.CustomResourceDefinition, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	var crds []*apiextensionsv1.CustomResourceDefinition
	for _, d := range strings.Split(string(data), "\n---\n") {
		// skip empty YAML documents
		if strings.TrimSpace(d) == "" {
			continue
		}

		internalCRD := &apiextensions.CustomResourceDefinition{}
		if _, _, err := codec.Decode([]byte(d), nil, internalCRD); err != nil {
			return nil, err
		}

		out := apiextensionsv1.CustomResourceDefinition{}
		if err := converter.Convert(internalCRD, &out, nil); err != nil {
			return nil, err
		}

		crds = append(crds, &out)
	}

	return crds, nil
}

func getValidatingWebhookConfig(url string, caPEM []byte) client.Object {
	failurePolicy := admissionregistrationv1.Fail
	sideEffects := admissionregistrationv1.SideEffectClassNone
	validateURL := fmt.Sprintf("%s/validate", url)
	webhook := admissionregistrationv1.ValidatingWebhookConfiguration{
		ObjectMeta: metav1.ObjectMeta{
			Name: "cert-manager-webhook",
		},
		Webhooks: []admissionregistrationv1.ValidatingWebhook{
			{
				Name: "webhook.cert-manager.io",
				ClientConfig: admissionregistrationv1.WebhookClientConfig{
					URL:      &validateURL,
					CABundle: caPEM,
				},
				Rules: []admissionregistrationv1.RuleWithOperations{
					{
						Operations: []admissionregistrationv1.OperationType{
							admissionregistrationv1.Create,
							admissionregistrationv1.Update,
						},
						Rule: admissionregistrationv1.Rule{
							APIGroups:   []string{"cert-manager.io", "acme.cert-manager.io"},
							APIVersions: []string{"*"},
							Resources:   []string{"*/*"},
						},
					},
				},
				FailurePolicy:           &failurePolicy,
				SideEffects:             &sideEffects,
				AdmissionReviewVersions: []string{"v1"},
			},
		},
	}

	return &webhook
}

func getMutatingWebhookConfig(url string, caPEM []byte) client.Object {
	failurePolicy := admissionregistrationv1.Fail
	sideEffects := admissionregistrationv1.SideEffectClassNone
	validateURL := fmt.Sprintf("%s/mutate", url)
	webhook := admissionregistrationv1.MutatingWebhookConfiguration{
		ObjectMeta: metav1.ObjectMeta{
			Name: "cert-manager-webhook",
		},
		Webhooks: []admissionregistrationv1.MutatingWebhook{
			{
				Name: "webhook.cert-manager.io",
				ClientConfig: admissionregistrationv1.WebhookClientConfig{
					URL:      &validateURL,
					CABundle: caPEM,
				},
				Rules: []admissionregistrationv1.RuleWithOperations{
					{
						Operations: []admissionregistrationv1.OperationType{
							admissionregistrationv1.Create,
							admissionregistrationv1.Update,
						},
						Rule: admissionregistrationv1.Rule{
							APIGroups:   []string{"cert-manager.io", "acme.cert-manager.io"},
							APIVersions: []string{"*"},
							Resources:   []string{"*/*"},
						},
					},
				},
				FailurePolicy:           &failurePolicy,
				SideEffects:             &sideEffects,
				AdmissionReviewVersions: []string{"v1"},
			},
		},
	}

	return &webhook
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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 api

import (
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/api/unversioned"
)

// Returns string version of ResourceName.
func (self ResourceName) String() string {
	return string(self)
}

// Returns the CPU limit if specified.
func (self *ResourceList) Cpu() *resource.Quantity {
	if val, ok := (*self)[ResourceCPU]; ok {
		return &val
	}
	return &resource.Quantity{Format: resource.DecimalSI}
}

// Returns the Memory limit if specified.
func (self *ResourceList) Memory() *resource.Quantity {
	if val, ok := (*self)[ResourceMemory]; ok {
		return &val
	}
	return &resource.Quantity{Format: resource.BinarySI}
}

func (self *ResourceList) Pods() *resource.Quantity {
	if val, ok := (*self)[ResourcePods]; ok {
		return &val
	}
	return &resource.Quantity{}
}

func (self *ResourceList) NvidiaGPU() *resource.Quantity {
	if val, ok := (*self)[ResourceNvidiaGPU]; ok {
		return &val
	}
	return &resource.Quantity{}
}

func GetContainerStatus(statuses []ContainerStatus, name string) (ContainerStatus, bool) {
	for i := range statuses {
		if statuses[i].Name == name {
			return statuses[i], true
		}
	}
	return ContainerStatus{}, false
}

func GetExistingContainerStatus(statuses []ContainerStatus, name string) ContainerStatus {
	for i := range statuses {
		if statuses[i].Name == name {
			return statuses[i]
		}
	}
	return ContainerStatus{}
}

// IsPodReady returns true if a pod is ready; false otherwise.
func IsPodReady(pod *Pod) bool {
	return IsPodReadyConditionTrue(pod.Status)
}

// IsPodReady retruns true if a pod is ready; false otherwise.
func IsPodReadyConditionTrue(status PodStatus) bool {
	condition := GetPodReadyCondition(status)
	return condition != nil && condition.Status == ConditionTrue
}

// Extracts the pod ready condition from the given status and returns that.
// Returns nil if the condition is not present.
func GetPodReadyCondition(status PodStatus) *PodCondition {
	_, condition := GetPodCondition(&status, PodReady)
	return condition
}

// GetPodCondition extracts the provided condition from the given status and returns that.
// Returns nil and -1 if the condition is not present, and the the index of the located condition.
func GetPodCondition(status *PodStatus, conditionType PodConditionType) (int, *PodCondition) {
	for i, c := range status.Conditions {
		if c.Type == conditionType {
			return i, &status.Conditions[i]
		}
	}
	return -1, nil
}

// Updates existing pod condition or creates a new one. Sets LastTransitionTime to now if the
// status has changed.
// Returns true if pod condition has changed or has been added.
func UpdatePodCondition(status *PodStatus, condition *PodCondition) bool {
	condition.LastTransitionTime = unversioned.Now()
	// Try to find this pod condition.
	conditionIndex, oldCondition := GetPodCondition(status, condition.Type)

	if oldCondition == nil {
		// We are adding new pod condition.
		status.Conditions = append(status.Conditions, *condition)
		return true
	} else {
		// We are updating an existing condition, so we need to check if it has changed.
		if condition.Status == oldCondition.Status {
			condition.LastTransitionTime = oldCondition.LastTransitionTime
		}

		isEqual := condition.Status == oldCondition.Status &&
			condition.Reason == oldCondition.Reason &&
			condition.Message == oldCondition.Message &&
			condition.LastProbeTime.Equal(oldCondition.LastProbeTime) &&
			condition.LastTransitionTime.Equal(oldCondition.LastTransitionTime)

		status.Conditions[conditionIndex] = *condition
		// Return true if one of the fields have changed.
		return !isEqual
	}
}

// IsNodeReady returns true if a node is ready; false otherwise.
func IsNodeReady(node *Node) bool {
	for _, c := range node.Status.Conditions {
		if c.Type == NodeReady {
			return c.Status == ConditionTrue
		}
	}
	return false
}

// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all
// containers of the pod.
func PodRequestsAndLimits(pod *Pod) (reqs map[ResourceName]resource.Quantity, limits map[ResourceName]resource.Quantity, err error) {
	reqs, limits = map[ResourceName]resource.Quantity{}, map[ResourceName]resource.Quantity{}
	for _, container := range pod.Spec.Containers {
		for name, quantity := range container.Resources.Requests {
			if value, ok := reqs[name]; !ok {
				reqs[name] = *quantity.Copy()
			} else if err = value.Add(quantity); err != nil {
				return nil, nil, err
			}
		}
		for name, quantity := range container.Resources.Limits {
			if value, ok := limits[name]; !ok {
				limits[name] = *quantity.Copy()
			} else if err = value.Add(quantity); err != nil {
				return nil, nil, err
			}
		}
	}
	return
}
<commit_msg>Describe and get should show init containers<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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 api

import (
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/api/unversioned"
)

// Returns string version of ResourceName.
func (self ResourceName) String() string {
	return string(self)
}

// Returns the CPU limit if specified.
func (self *ResourceList) Cpu() *resource.Quantity {
	if val, ok := (*self)[ResourceCPU]; ok {
		return &val
	}
	return &resource.Quantity{Format: resource.DecimalSI}
}

// Returns the Memory limit if specified.
func (self *ResourceList) Memory() *resource.Quantity {
	if val, ok := (*self)[ResourceMemory]; ok {
		return &val
	}
	return &resource.Quantity{Format: resource.BinarySI}
}

func (self *ResourceList) Pods() *resource.Quantity {
	if val, ok := (*self)[ResourcePods]; ok {
		return &val
	}
	return &resource.Quantity{}
}

func (self *ResourceList) NvidiaGPU() *resource.Quantity {
	if val, ok := (*self)[ResourceNvidiaGPU]; ok {
		return &val
	}
	return &resource.Quantity{}
}

func GetContainerStatus(statuses []ContainerStatus, name string) (ContainerStatus, bool) {
	for i := range statuses {
		if statuses[i].Name == name {
			return statuses[i], true
		}
	}
	return ContainerStatus{}, false
}

func GetExistingContainerStatus(statuses []ContainerStatus, name string) ContainerStatus {
	for i := range statuses {
		if statuses[i].Name == name {
			return statuses[i]
		}
	}
	return ContainerStatus{}
}

// IsPodReady returns true if a pod is ready; false otherwise.
func IsPodReady(pod *Pod) bool {
	return IsPodReadyConditionTrue(pod.Status)
}

// IsPodReady retruns true if a pod is ready; false otherwise.
func IsPodReadyConditionTrue(status PodStatus) bool {
	condition := GetPodReadyCondition(status)
	return condition != nil && condition.Status == ConditionTrue
}

// Extracts the pod ready condition from the given status and returns that.
// Returns nil if the condition is not present.
func GetPodReadyCondition(status PodStatus) *PodCondition {
	_, condition := GetPodCondition(&status, PodReady)
	return condition
}

// GetPodCondition extracts the provided condition from the given status and returns that.
// Returns nil and -1 if the condition is not present, and the the index of the located condition.
func GetPodCondition(status *PodStatus, conditionType PodConditionType) (int, *PodCondition) {
	for i, c := range status.Conditions {
		if c.Type == conditionType {
			return i, &status.Conditions[i]
		}
	}
	return -1, nil
}

// Updates existing pod condition or creates a new one. Sets LastTransitionTime to now if the
// status has changed.
// Returns true if pod condition has changed or has been added.
func UpdatePodCondition(status *PodStatus, condition *PodCondition) bool {
	condition.LastTransitionTime = unversioned.Now()
	// Try to find this pod condition.
	conditionIndex, oldCondition := GetPodCondition(status, condition.Type)

	if oldCondition == nil {
		// We are adding new pod condition.
		status.Conditions = append(status.Conditions, *condition)
		return true
	} else {
		// We are updating an existing condition, so we need to check if it has changed.
		if condition.Status == oldCondition.Status {
			condition.LastTransitionTime = oldCondition.LastTransitionTime
		}

		isEqual := condition.Status == oldCondition.Status &&
			condition.Reason == oldCondition.Reason &&
			condition.Message == oldCondition.Message &&
			condition.LastProbeTime.Equal(oldCondition.LastProbeTime) &&
			condition.LastTransitionTime.Equal(oldCondition.LastTransitionTime)

		status.Conditions[conditionIndex] = *condition
		// Return true if one of the fields have changed.
		return !isEqual
	}
}

// IsNodeReady returns true if a node is ready; false otherwise.
func IsNodeReady(node *Node) bool {
	for _, c := range node.Status.Conditions {
		if c.Type == NodeReady {
			return c.Status == ConditionTrue
		}
	}
	return false
}

// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all
// containers of the pod.
func PodRequestsAndLimits(pod *Pod) (reqs map[ResourceName]resource.Quantity, limits map[ResourceName]resource.Quantity, err error) {
	reqs, limits = map[ResourceName]resource.Quantity{}, map[ResourceName]resource.Quantity{}
	for _, container := range pod.Spec.Containers {
		for name, quantity := range container.Resources.Requests {
			if value, ok := reqs[name]; !ok {
				reqs[name] = *quantity.Copy()
			} else if err = value.Add(quantity); err != nil {
				return nil, nil, err
			}
		}
		for name, quantity := range container.Resources.Limits {
			if value, ok := limits[name]; !ok {
				limits[name] = *quantity.Copy()
			} else if err = value.Add(quantity); err != nil {
				return nil, nil, err
			}
		}
	}
	// init containers define the minimum of any resource
	for _, container := range pod.Spec.InitContainers {
		for name, quantity := range container.Resources.Requests {
			value, ok := reqs[name]
			if !ok {
				reqs[name] = *quantity.Copy()
				continue
			}
			if quantity.Cmp(value) > 0 {
				reqs[name] = *quantity.Copy()
			}
		}
		for name, quantity := range container.Resources.Limits {
			value, ok := limits[name]
			if !ok {
				limits[name] = *quantity.Copy()
				continue
			}
			if quantity.Cmp(value) > 0 {
				limits[name] = *quantity.Copy()
			}
		}
	}
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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 api

import (
	"k8s.io/kubernetes/pkg/api/resource"
)

// Returns string version of ResourceName.
func (self ResourceName) String() string {
	return string(self)
}

// Returns the CPU limit if specified.
func (self *ResourceList) Cpu() *resource.Quantity {
	if val, ok := (*self)[ResourceCPU]; ok {
		return &val
	}
	return &resource.Quantity{}
}

// Returns the Memory limit if specified.
func (self *ResourceList) Memory() *resource.Quantity {
	if val, ok := (*self)[ResourceMemory]; ok {
		return &val
	}
	return &resource.Quantity{}
}

func (self *ResourceList) Pods() *resource.Quantity {
	if val, ok := (*self)[ResourcePods]; ok {
		return &val
	}
	return &resource.Quantity{}
}

func GetContainerStatus(statuses []ContainerStatus, name string) (ContainerStatus, bool) {
	for i := range statuses {
		if statuses[i].Name == name {
			return statuses[i], true
		}
	}
	return ContainerStatus{}, false
}

func GetExistingContainerStatus(statuses []ContainerStatus, name string) ContainerStatus {
	for i := range statuses {
		if statuses[i].Name == name {
			return statuses[i]
		}
	}
	return ContainerStatus{}
}

// IsPodReady returns true if a pod is ready; false otherwise.
func IsPodReady(pod *Pod) bool {
	return IsPodReadyConditionTrue(pod.Status)
}

// IsPodReady retruns true if a pod is ready; false otherwise.
func IsPodReadyConditionTrue(status PodStatus) bool {
	condition := GetPodReadyCondition(status)
	return condition != nil && condition.Status == ConditionTrue
}

// Extracts the pod ready condition from the given status and returns that.
// Returns nil if the condition is not present.
func GetPodReadyCondition(status PodStatus) *PodCondition {
	for i, c := range status.Conditions {
		if c.Type == PodReady {
			return &status.Conditions[i]
		}
	}
	return nil
}

// IsNodeReady returns true if a node is ready; false otherwise.
func IsNodeReady(node *Node) bool {
	for _, c := range node.Status.Conditions {
		if c.Type == NodeReady {
			return c.Status == ConditionTrue
		}
	}
	return false
}
<commit_msg>api, kubectl: move getSinglePodTotalRequestsAndLimits to api.PodRequestsAndLimits<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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 api

import (
	"k8s.io/kubernetes/pkg/api/resource"
)

// Returns string version of ResourceName.
func (self ResourceName) String() string {
	return string(self)
}

// Returns the CPU limit if specified.
func (self *ResourceList) Cpu() *resource.Quantity {
	if val, ok := (*self)[ResourceCPU]; ok {
		return &val
	}
	return &resource.Quantity{}
}

// Returns the Memory limit if specified.
func (self *ResourceList) Memory() *resource.Quantity {
	if val, ok := (*self)[ResourceMemory]; ok {
		return &val
	}
	return &resource.Quantity{}
}

func (self *ResourceList) Pods() *resource.Quantity {
	if val, ok := (*self)[ResourcePods]; ok {
		return &val
	}
	return &resource.Quantity{}
}

func GetContainerStatus(statuses []ContainerStatus, name string) (ContainerStatus, bool) {
	for i := range statuses {
		if statuses[i].Name == name {
			return statuses[i], true
		}
	}
	return ContainerStatus{}, false
}

func GetExistingContainerStatus(statuses []ContainerStatus, name string) ContainerStatus {
	for i := range statuses {
		if statuses[i].Name == name {
			return statuses[i]
		}
	}
	return ContainerStatus{}
}

// IsPodReady returns true if a pod is ready; false otherwise.
func IsPodReady(pod *Pod) bool {
	return IsPodReadyConditionTrue(pod.Status)
}

// IsPodReady retruns true if a pod is ready; false otherwise.
func IsPodReadyConditionTrue(status PodStatus) bool {
	condition := GetPodReadyCondition(status)
	return condition != nil && condition.Status == ConditionTrue
}

// Extracts the pod ready condition from the given status and returns that.
// Returns nil if the condition is not present.
func GetPodReadyCondition(status PodStatus) *PodCondition {
	for i, c := range status.Conditions {
		if c.Type == PodReady {
			return &status.Conditions[i]
		}
	}
	return nil
}

// IsNodeReady returns true if a node is ready; false otherwise.
func IsNodeReady(node *Node) bool {
	for _, c := range node.Status.Conditions {
		if c.Type == NodeReady {
			return c.Status == ConditionTrue
		}
	}
	return false
}

// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all
// containers of the pod.
func PodRequestsAndLimits(pod *Pod) (reqs map[ResourceName]resource.Quantity, limits map[ResourceName]resource.Quantity, err error) {
	reqs, limits = map[ResourceName]resource.Quantity{}, map[ResourceName]resource.Quantity{}
	for _, container := range pod.Spec.Containers {
		for name, quantity := range container.Resources.Requests {
			if value, ok := reqs[name]; !ok {
				reqs[name] = *quantity.Copy()
			} else if err = value.Add(quantity); err != nil {
				return nil, nil, err
			}
		}
		for name, quantity := range container.Resources.Limits {
			if value, ok := limits[name]; !ok {
				limits[name] = *quantity.Copy()
			} else if err = value.Add(quantity); err != nil {
				return nil, nil, err
			}
		}
	}
	return
}<|endoftext|>


<commit_before>package api

import (
	"time"

	r "github.com/pgpst/pgpst/internal/github.com/dancannon/gorethink"
	"github.com/pgpst/pgpst/internal/github.com/dchest/uniuri"
	"github.com/pgpst/pgpst/internal/github.com/gin-gonic/gin"

	"github.com/pgpst/pgpst/pkg/models"
)

func (a *API) createResource(c *gin.Context) {
	// Get token and account info from the context
	var (
		account = c.MustGet("account").(*models.Account)
		token   = c.MustGet("token").(*models.Token)
	)

	// Check the scope
	if !models.InScope(token.Scope, []string{"resources:create"}) {
		c.JSON(403, &gin.H{
			"code":  0,
			"error": "Your token has insufficient scope",
		})
		return
	}

	// Decode the input
	var input struct {
		Meta map[string]interface{} `json:"meta"`
		Body []byte                 `json:"body"`
		Tags []string               `json:"tags"`
	}
	if err := c.Bind(&input); err != nil {
		c.JSON(422, &gin.H{
			"code":    0,
			"message": err.Error(),
		})
		return
	}

	// Save it into database
	resource := &models.Resource{
		ID:           uniuri.NewLen(uniuri.UUIDLen),
		DateCreated:  time.Now(),
		DateModified: time.Now(),
		Owner:        account.ID,

		Meta: input.Meta,
		Body: input.Body,
		Tags: input.Tags,
	}

	// Insert it into database
	if err := r.Table("resources").Insert(resource).Exec(a.Rethink); err != nil {
		c.JSON(500, &gin.H{
			"code":    0,
			"message": err.Error(),
		})
		return
	}

	c.JSON(201, resource)
}

func (a *API) getAccountResources(c *gin.Context) {
	// Token and account from context
	var (
		ownAccount = c.MustGet("account").(*models.Account)
		token      = c.MustGet("token").(*models.Token)
	)

	// Resolve the ID from the URL
	id := c.Param("id")
	if id == "me" {
		id = ownAccount.ID
	}

	// Check the scope
	if id == ownAccount.ID {
		if !models.InScope(token.Scope, []string{"resources:read"}) {
			c.JSON(403, &gin.H{
				"code":  0,
				"error": "Your token has insufficient scope",
			})
			return
		}
	} else {
		if !models.InScope(token.Scope, []string{"admin"}) {
			c.JSON(403, &gin.H{
				"code":  0,
				"error": "Your token has insufficient scope",
			})
			return
		}
	}

	// Get resources from database without bodies
	cursor, err := r.Table("resources").GetAllByIndex("owner", id).Without("body").Run(a.Rethink)
	if err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}
	defer cursor.Close()
	var resources []*models.Resource
	if err := cursor.All(&resources); err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}

	// Write the response
	c.JSON(200, resources)
	return
}

func (a *API) readResource(c *gin.Context) {
	// Get token and account info from the context
	var (
		account = c.MustGet("account").(*models.Account)
		token   = c.MustGet("token").(*models.Token)
	)

	// Resolve the resource ID and fetch it from database
	id := c.Param("id")
	cursor, err := r.Table("resources").Get(id).Run(a.Rethink)
	if err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}
	defer cursor.Close()
	var resource *models.Resource
	if err := cursor.All(&resource); err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}

	if resource.Owner == account.ID {
		// Check the scope
		if !models.InScope(token.Scope, []string{"resources:read"}) {
			c.JSON(403, &gin.H{
				"code":  0,
				"error": "Your token has insufficient scope",
			})
			return
		}
	} else {
		// Check the scope
		if !models.InScope(token.Scope, []string{"admin"}) {
			c.JSON(403, &gin.H{
				"code":  0,
				"error": "Your token has insufficient scope",
			})
			return
		}
	}

	c.JSON(200, resource)
}
<commit_msg>Resource searching implementation<commit_after>package api

import (
	"strconv"
	"strings"
	"time"

	r "github.com/pgpst/pgpst/internal/github.com/dancannon/gorethink"
	"github.com/pgpst/pgpst/internal/github.com/dchest/uniuri"
	"github.com/pgpst/pgpst/internal/github.com/gin-gonic/gin"

	"github.com/pgpst/pgpst/pkg/models"
)

func (a *API) createResource(c *gin.Context) {
	// Get token and account info from the context
	var (
		account = c.MustGet("account").(*models.Account)
		token   = c.MustGet("token").(*models.Token)
	)

	// Check the scope
	if !models.InScope(token.Scope, []string{"resources:create"}) {
		c.JSON(403, &gin.H{
			"code":  0,
			"error": "Your token has insufficient scope",
		})
		return
	}

	// Decode the input
	var input struct {
		Meta map[string]interface{} `json:"meta"`
		Body []byte                 `json:"body"`
		Tags []string               `json:"tags"`
	}
	if err := c.Bind(&input); err != nil {
		c.JSON(422, &gin.H{
			"code":    0,
			"message": err.Error(),
		})
		return
	}

	// Save it into database
	resource := &models.Resource{
		ID:           uniuri.NewLen(uniuri.UUIDLen),
		DateCreated:  time.Now(),
		DateModified: time.Now(),
		Owner:        account.ID,

		Meta: input.Meta,
		Body: input.Body,
		Tags: input.Tags,
	}

	// Insert it into database
	if err := r.Table("resources").Insert(resource).Exec(a.Rethink); err != nil {
		c.JSON(500, &gin.H{
			"code":    0,
			"message": err.Error(),
		})
		return
	}

	c.JSON(201, resource)
}

func (a *API) getAccountResources(c *gin.Context) {
	// Token and account from context
	var (
		ownAccount = c.MustGet("account").(*models.Account)
		token      = c.MustGet("token").(*models.Token)
	)

	// Resolve the ID from the URL
	id := c.Param("id")
	if id == "me" {
		id = ownAccount.ID
	}

	// Check the scope
	if id == ownAccount.ID {
		if !models.InScope(token.Scope, []string{"resources:read"}) {
			c.JSON(403, &gin.H{
				"code":  0,
				"error": "Your token has insufficient scope",
			})
			return
		}
	} else {
		if !models.InScope(token.Scope, []string{"admin"}) {
			c.JSON(403, &gin.H{
				"code":  0,
				"error": "Your token has insufficient scope",
			})
			return
		}
	}

	// 1. Owner filter
	query := r.Table("resources").GetAllByIndex("owner", id).Without("body")

	// 2. Tag filter
	if tagstr := c.Query("tags"); tagstr != "" {
		tags := strings.Split(tagstr, ",")

		// Cast to []interface{}
		tagsi := []interface{}{}
		for _, tag := range tags {
			tagsi = append(tagsi, tag)
		}

		query = query.Filter(func(row r.Term) r.Term {
			return row.Field("tags").Contains(tagsi...)
		})
	}

	// 3. Meta filter
	// not needed right now

	// 4. Date created and date modified
	ts := func(field string) error {
		if dm := c.Query(field); dm != "" {
			dmp := strings.Split(dm, ",")
			if len(dmp) == 1 || dmp[1] == "" {
				// parse dmp[0]
				d0, err := time.Parse(time.RFC3339, dmp[0])
				if err != nil {
					return err
				}

				// after dmp[0]
				query = query.Filter(func(row r.Term) r.Term {
					return row.Field(field).Ge(d0)
				})
			} else {
				// parse dmp[1]
				d1, err := time.Parse(time.RFC3339, dmp[1])
				if err != nil {
					return err
				}

				if dmp[0] == "" {
					// until dmp[1]
					query = query.Filter(func(row r.Term) r.Term {
						return row.Field(field).Le(d1)
					})
				} else {
					// parse dmp[0]
					d0, err := time.Parse(time.RFC3339, dmp[0])
					if err != nil {
						return err
					}

					// between dmp[0] and dmp[1]
					query = query.Filter(func(row r.Term) r.Term {
						return row.Field(field).Ge(d0).And(row.Field(field).Le(d1))
					})
				}
			}
		}

		return nil
	}
	if err := ts("date_modified"); err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}
	if err := ts("date_created"); err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}

	// 5. Pluck / Without
	if pls := c.Query("pluck"); pls != "" {
		pl := strings.Split(pls, ",")
		// Cast to []interface{}
		pli := []interface{}{}
		for _, field := range pl {
			pli = append(pli, field)
		}
		query = query.Pluck(pli...)
	} else if wos := c.Query("wo"); wos != "" {
		wo := strings.Split(wos, ",")
		// Cast to []interface{}
		woi := []interface{}{}
		for _, field := range wo {
			woi = append(woi, field)
		}
		query = query.Pluck(woi...)
	}

	// 6. Ordering
	if obs := c.Query("order_by"); obs != "" {
		ob := strings.Split(obs, ",")
		fields := []interface{}{}
		for _, fi := range ob {
			asc := true
			if fi[0] == '-' {
				asc = false
				fi = fi[1:]
			} else if fi[0] == '+' || fi[0] == ' ' {
				fi = fi[1:]
			}

			field := r.Row.Field(fi)

			if path := strings.Split(fi, "."); len(path) > 1 {
				field = r.Row.Field(path[0])

				for i := 1; i < len(path); i++ {
					field = field.Field(path[i])
				}
			}

			if !asc {
				field = r.Desc(field)
			}

			fields = append(fields, field)
		}

		query = query.OrderBy(fields...)
	}

	// 7. Limiting
	var (
		sks         = c.Query("skip")
		lms         = c.Query("limit")
		err         error
		skip, limit int
	)
	if sks != "" {
		skip, err = strconv.Atoi(sks)
		if err != nil {
			c.JSON(400, &gin.H{
				"code":  0,
				"error": err.Error(),
			})
			return
		}
	}

	if lms != "" {
		limit, err = strconv.Atoi(lms)
		if err != nil {
			c.JSON(400, &gin.H{
				"code":  0,
				"error": err.Error(),
			})
			return
		}
	}

	if skip != 0 && limit != 0 {
		query = query.Slice(skip, skip+limit)
	} else if skip == 0 && limit != 0 {
		query = query.Limit(limit)
	} else if skip != 0 && limit == 0 {
		query = query.Skip(skip)
	}

	// Get resources from database without bodies
	cursor, err := query.Run(a.Rethink)
	if err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}
	defer cursor.Close()
	var resources []*models.Resource
	if err := cursor.All(&resources); err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}

	// Write the response
	c.JSON(200, resources)
	return
}

func (a *API) readResource(c *gin.Context) {
	// Get token and account info from the context
	var (
		account = c.MustGet("account").(*models.Account)
		token   = c.MustGet("token").(*models.Token)
	)

	// Resolve the resource ID and fetch it from database
	id := c.Param("id")
	cursor, err := r.Table("resources").Get(id).Run(a.Rethink)
	if err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}
	defer cursor.Close()
	var resource *models.Resource
	if err := cursor.All(&resource); err != nil {
		c.JSON(500, &gin.H{
			"code":  0,
			"error": err.Error(),
		})
		return
	}

	if resource.Owner == account.ID {
		// Check the scope
		if !models.InScope(token.Scope, []string{"resources:read"}) {
			c.JSON(403, &gin.H{
				"code":  0,
				"error": "Your token has insufficient scope",
			})
			return
		}
	} else {
		// Check the scope
		if !models.InScope(token.Scope, []string{"admin"}) {
			c.JSON(403, &gin.H{
				"code":  0,
				"error": "Your token has insufficient scope",
			})
			return
		}
	}

	c.JSON(200, resource)
}
<|endoftext|>


<commit_before>// Copyright 2016-2019 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package watchers

import (
	"github.com/cilium/cilium/pkg/k8s"
	"github.com/cilium/cilium/pkg/k8s/informer"
	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/core/v1"
	"github.com/cilium/cilium/pkg/lock"

	v1 "k8s.io/api/core/v1"
	v1meta "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
)

func (k *K8sWatcher) servicesInit(k8sClient kubernetes.Interface, swgSvcs *lock.StoppableWaitGroup, optsModifier func(*v1meta.ListOptions)) {
	_, svcController := informer.NewInformer(
		cache.NewFilteredListWatchFromClient(k8sClient.CoreV1().RESTClient(),
			"services", v1.NamespaceAll, optsModifier),
		&slim_corev1.Service{},
		0,
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				var valid, equal bool
				defer func() { k.K8sEventReceived(metricService, metricCreate, valid, equal) }()
				if k8sSvc := k8s.ObjToV1Services(obj); k8sSvc != nil {
					valid = true
					err := k.addK8sServiceV1(k8sSvc, swgSvcs)
					k.K8sEventProcessed(metricService, metricCreate, err == nil)
				}
			},
			UpdateFunc: func(oldObj, newObj interface{}) {
				var valid, equal bool
				defer func() { k.K8sEventReceived(metricService, metricUpdate, valid, equal) }()
				if oldk8sSvc := k8s.ObjToV1Services(oldObj); oldk8sSvc != nil {
					if newk8sSvc := k8s.ObjToV1Services(newObj); newk8sSvc != nil {
						valid = true
						if k8s.EqualV1Services(oldk8sSvc, newk8sSvc, k.datapath.LocalNodeAddressing()) {
							equal = true
							return
						}

						err := k.updateK8sServiceV1(oldk8sSvc, newk8sSvc, swgSvcs)
						k.K8sEventProcessed(metricService, metricUpdate, err == nil)
					}
				}
			},
			DeleteFunc: func(obj interface{}) {
				var valid, equal bool
				defer func() { k.K8sEventReceived(metricService, metricDelete, valid, equal) }()
				k8sSvc := k8s.ObjToV1Services(obj)
				if k8sSvc == nil {
					return
				}

				valid = true
				err := k.deleteK8sServiceV1(k8sSvc, swgSvcs)
				k.K8sEventProcessed(metricService, metricDelete, err == nil)
			},
		},
		nil,
	)
	k.blockWaitGroupToSyncResources(wait.NeverStop, swgSvcs, svcController.HasSynced, K8sAPIGroupServiceV1Core)
	go svcController.Run(wait.NeverStop)
	k.k8sAPIGroups.addAPI(K8sAPIGroupServiceV1Core)
}

func (k *K8sWatcher) addK8sServiceV1(svc *slim_corev1.Service, swg *lock.StoppableWaitGroup) error {
	svcID := k.K8sSvcCache.UpdateService(svc, swg)
	if svc.Spec.Type == slim_corev1.ServiceTypeClusterIP {
		// The local redirect policies currently support services of type
		// clusterIP only.
		k.redirectPolicyManager.OnAddService(svcID, k.podStore)
	}
	return nil
}

func (k *K8sWatcher) updateK8sServiceV1(oldSvc, newSvc *slim_corev1.Service, swg *lock.StoppableWaitGroup) error {
	return k.addK8sServiceV1(newSvc, swg)
}

func (k *K8sWatcher) deleteK8sServiceV1(svc *slim_corev1.Service, swg *lock.StoppableWaitGroup) error {
	k.K8sSvcCache.DeleteService(svc, swg)
	svcID := k8s.ParseServiceID(svc)
	if svc.Spec.Type == slim_corev1.ServiceTypeClusterIP {
		k.redirectPolicyManager.OnDeleteService(svcID)
	}
	return nil
}
<commit_msg>k8s/watchers: fix data race in (*K8sWatcher).addK8sServiceV1<commit_after>// Copyright 2016-2019 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package watchers

import (
	"github.com/cilium/cilium/pkg/k8s"
	"github.com/cilium/cilium/pkg/k8s/informer"
	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/core/v1"
	"github.com/cilium/cilium/pkg/lock"

	v1 "k8s.io/api/core/v1"
	v1meta "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
)

func (k *K8sWatcher) servicesInit(k8sClient kubernetes.Interface, swgSvcs *lock.StoppableWaitGroup, optsModifier func(*v1meta.ListOptions)) {
	_, svcController := informer.NewInformer(
		cache.NewFilteredListWatchFromClient(k8sClient.CoreV1().RESTClient(),
			"services", v1.NamespaceAll, optsModifier),
		&slim_corev1.Service{},
		0,
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				var valid, equal bool
				defer func() { k.K8sEventReceived(metricService, metricCreate, valid, equal) }()
				if k8sSvc := k8s.ObjToV1Services(obj); k8sSvc != nil {
					valid = true
					err := k.addK8sServiceV1(k8sSvc, swgSvcs)
					k.K8sEventProcessed(metricService, metricCreate, err == nil)
				}
			},
			UpdateFunc: func(oldObj, newObj interface{}) {
				var valid, equal bool
				defer func() { k.K8sEventReceived(metricService, metricUpdate, valid, equal) }()
				if oldk8sSvc := k8s.ObjToV1Services(oldObj); oldk8sSvc != nil {
					if newk8sSvc := k8s.ObjToV1Services(newObj); newk8sSvc != nil {
						valid = true
						if k8s.EqualV1Services(oldk8sSvc, newk8sSvc, k.datapath.LocalNodeAddressing()) {
							equal = true
							return
						}

						err := k.updateK8sServiceV1(oldk8sSvc, newk8sSvc, swgSvcs)
						k.K8sEventProcessed(metricService, metricUpdate, err == nil)
					}
				}
			},
			DeleteFunc: func(obj interface{}) {
				var valid, equal bool
				defer func() { k.K8sEventReceived(metricService, metricDelete, valid, equal) }()
				k8sSvc := k8s.ObjToV1Services(obj)
				if k8sSvc == nil {
					return
				}

				valid = true
				err := k.deleteK8sServiceV1(k8sSvc, swgSvcs)
				k.K8sEventProcessed(metricService, metricDelete, err == nil)
			},
		},
		nil,
	)
	k.blockWaitGroupToSyncResources(wait.NeverStop, swgSvcs, svcController.HasSynced, K8sAPIGroupServiceV1Core)
	go svcController.Run(wait.NeverStop)
	k.k8sAPIGroups.addAPI(K8sAPIGroupServiceV1Core)
}

func (k *K8sWatcher) addK8sServiceV1(svc *slim_corev1.Service, swg *lock.StoppableWaitGroup) error {
	svcID := k.K8sSvcCache.UpdateService(svc, swg)
	if svc.Spec.Type == slim_corev1.ServiceTypeClusterIP {
		// The local redirect policies currently support services of type
		// clusterIP only.
		k.podStoreMU.RLock()
		k.redirectPolicyManager.OnAddService(svcID, k.podStore)
		k.podStoreMU.RUnlock()
	}
	return nil
}

func (k *K8sWatcher) updateK8sServiceV1(oldSvc, newSvc *slim_corev1.Service, swg *lock.StoppableWaitGroup) error {
	return k.addK8sServiceV1(newSvc, swg)
}

func (k *K8sWatcher) deleteK8sServiceV1(svc *slim_corev1.Service, swg *lock.StoppableWaitGroup) error {
	k.K8sSvcCache.DeleteService(svc, swg)
	svcID := k8s.ParseServiceID(svc)
	if svc.Spec.Type == slim_corev1.ServiceTypeClusterIP {
		k.redirectPolicyManager.OnDeleteService(svcID)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"

	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/io"
)

// Usage:
// go run kpub.go -z test -c test -t test -ack all -n 1024 -sz 25
// gk peek -z test -c test -t test -body > test.txt
// go run validator.go test.txt
func main() {
	if len(os.Args) != 2 {
		panic("Usage go run validator.go <filename>")
	}

	f, err := os.Open(os.Args[1])
	if err != nil {
		panic(err)
	}

	min, max := 1<<10, 0
	lineN := 0
	reader := bufio.NewReader(f)
	last := -98734
	for {
		l, err := io.ReadLine(reader)
		if err != nil {
			// EOF
			break
		}

		// {000000019} XXXXXXXXXXXXXXXXXXXXXXXXX
		n, err := strconv.Atoi(string(l[1:10]))
		if err != nil {
			fmt.Printf("%s for `%s`\n", err, string(l))
			break
		}

		if n > max {
			max = n
		}
		if n < min {
			min = n
		}

		if last >= 0 && n != last+1 {
			fmt.Println(color.Red("%d %d", last, n))
		}

		last = n
	}

	fmt.Printf("min=%d, max=%d, lines=%d\n", min, max, lineN)
	if lineN-max == 1 {
		// kpub always starts with 0
		fmt.Println("passed")
	} else {
		fmt.Println("failed")
	}
}
<commit_msg>bug fix: forgot to inc the lineN<commit_after>package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"

	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/io"
)

// Usage:
// go run kpub.go -z test -c test -t test -ack all -n 1024 -sz 25
// gk peek -z test -c test -t test -body > test.txt
// go run validator.go test.txt
func main() {
	if len(os.Args) != 2 {
		panic("Usage go run validator.go <filename>")
	}

	f, err := os.Open(os.Args[1])
	if err != nil {
		panic(err)
	}

	min, max := 1<<10, 0
	lineN := 0
	reader := bufio.NewReader(f)
	last := -98734
	for {
		l, err := io.ReadLine(reader)
		if err != nil {
			// EOF
			break
		}

		lineN++

		// {000000019} XXXXXXXXXXXXXXXXXXXXXXXXX
		n, err := strconv.Atoi(string(l[1:10]))
		if err != nil {
			fmt.Printf("%s for `%s`\n", err, string(l))
			break
		}

		if n > max {
			max = n
		}
		if n < min {
			min = n
		}

		if last >= 0 && n != last+1 {
			fmt.Println(color.Red("%d %d", last, n))
		}

		last = n
	}

	fmt.Printf("min=%d, max=%d, lines=%d\n", min, max, lineN)
	if lineN-max == 1 {
		// kpub always starts with 0
		fmt.Println("passed")
	} else {
		fmt.Println("failed")
	}
}
<|endoftext|>


<commit_before>package client

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/gorilla/websocket"
	json "github.com/json-iterator/go"
	"github.com/prometheus/common/config"

	"github.com/grafana/loki/pkg/loghttp"
	"github.com/grafana/loki/pkg/logproto"
	"github.com/grafana/loki/pkg/util"
)

const (
	queryPath       = "/loki/api/v1/query?query=%s&limit=%d&time=%d&direction=%s"
	queryRangePath  = "/loki/api/v1/query_range"
	labelsPath      = "/loki/api/v1/labels"
	labelValuesPath = "/loki/api/v1/label/%s/values"
	tailPath        = "/loki/api/v1/tail?query=%s&delay_for=%d&limit=%d&start=%d"
)

// Client contains fields necessary to query a Loki instance
type Client struct {
	TLSConfig config.TLSConfig
	Username  string
	Password  string
	Address   string
	OrgID     string
}

// Query uses the /api/v1/query endpoint to execute an instant query
// excluding interfacer b/c it suggests taking the interface promql.Node instead of logproto.Direction b/c it happens to have a String() method
// nolint:interfacer
func (c *Client) Query(queryStr string, limit int, time time.Time, direction logproto.Direction, quiet bool) (*loghttp.QueryResponse, error) {
	path := fmt.Sprintf(queryPath,
		url.QueryEscape(queryStr), // query
		limit,                     // limit
		time.UnixNano(),           // start
		direction.String(),        // direction
	)

	return c.doQuery(path, quiet)
}

// QueryRange uses the /api/v1/query_range endpoint to execute a range query
// excluding interfacer b/c it suggests taking the interface promql.Node instead of logproto.Direction b/c it happens to have a String() method
// nolint:interfacer
func (c *Client) QueryRange(queryStr string, limit int, from, through time.Time, direction logproto.Direction, step time.Duration, quiet bool) (*loghttp.QueryResponse, error) {
	params := util.NewQueryStringBuilder()
	params.SetString("query", queryStr)
	params.SetInt32("limit", limit)
	params.SetInt("start", from.UnixNano())
	params.SetInt("end", through.UnixNano())
	params.SetString("direction", direction.String())

	// The step is optional, so we do set it only if provided,
	// otherwise we do leverage on the API defaults
	if step != 0 {
		params.SetInt("step", int64(step.Seconds()))
	}

	return c.doQuery(params.EncodeWithPath(queryRangePath), quiet)
}

// ListLabelNames uses the /api/v1/label endpoint to list label names
func (c *Client) ListLabelNames(quiet bool) (*loghttp.LabelResponse, error) {
	var labelResponse loghttp.LabelResponse
	if err := c.doRequest(labelsPath, quiet, &labelResponse); err != nil {
		return nil, err
	}
	return &labelResponse, nil
}

// ListLabelValues uses the /api/v1/label endpoint to list label values
func (c *Client) ListLabelValues(name string, quiet bool) (*loghttp.LabelResponse, error) {
	path := fmt.Sprintf(labelValuesPath, url.PathEscape(name))
	var labelResponse loghttp.LabelResponse
	if err := c.doRequest(path, quiet, &labelResponse); err != nil {
		return nil, err
	}
	return &labelResponse, nil
}

func (c *Client) doQuery(path string, quiet bool) (*loghttp.QueryResponse, error) {
	var err error
	var r loghttp.QueryResponse

	if err = c.doRequest(path, quiet, &r); err != nil {
		return nil, err
	}

	return &r, nil
}

func (c *Client) doRequest(path string, quiet bool, out interface{}) error {
	us := c.Address + path
	if !quiet {
		log.Print(us)
	}

	req, err := http.NewRequest("GET", us, nil)
	if err != nil {
		return err
	}

	req.SetBasicAuth(c.Username, c.Password)

	if c.OrgID != "" {
		req.Header.Set("X-Scope-OrgID", c.OrgID)
	}

	// Parse the URL to extract the host
	clientConfig := config.HTTPClientConfig{
		TLSConfig: c.TLSConfig,
	}

	client, err := config.NewClientFromConfig(clientConfig, "logcli", false)
	if err != nil {
		return err
	}

	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			log.Println("error closing body", err)
		}
	}()

	if resp.StatusCode/100 != 2 {
		buf, _ := ioutil.ReadAll(resp.Body) // nolint
		return fmt.Errorf("Error response from server: %s (%v)", string(buf), err)
	}

	return json.NewDecoder(resp.Body).Decode(out)
}

// LiveTailQueryConn uses /api/prom/tail to set up a websocket connection and returns it
func (c *Client) LiveTailQueryConn(queryStr string, delayFor int, limit int, from int64, quiet bool) (*websocket.Conn, error) {
	path := fmt.Sprintf(tailPath,
		url.QueryEscape(queryStr), // query
		delayFor,                  // delay_for
		limit,                     // limit
		from,                      // start
	)
	return c.wsConnect(path, quiet)
}

func (c *Client) wsConnect(path string, quiet bool) (*websocket.Conn, error) {
	us := c.Address + path

	tlsConfig, err := config.NewTLSConfig(&c.TLSConfig)
	if err != nil {
		return nil, err
	}

	if strings.HasPrefix(us, "https") {
		us = strings.Replace(us, "https", "wss", 1)
	} else if strings.HasPrefix(us, "http") {
		us = strings.Replace(us, "http", "ws", 1)
	}
	if !quiet {
		log.Println(us)
	}

	h := http.Header{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte(c.Username+":"+c.Password))}}

	ws := websocket.Dialer{
		TLSClientConfig: tlsConfig,
	}

	conn, resp, err := ws.Dial(us, h)

	if err != nil {
		if resp == nil {
			return nil, err
		}
		buf, _ := ioutil.ReadAll(resp.Body) // nolint
		return nil, fmt.Errorf("Error response from server: %s (%v)", string(buf), err)
	}

	return conn, nil
}
<commit_msg>logcli client: use OrgID in LiveTail (#1850)<commit_after>package client

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/gorilla/websocket"
	json "github.com/json-iterator/go"
	"github.com/prometheus/common/config"

	"github.com/grafana/loki/pkg/loghttp"
	"github.com/grafana/loki/pkg/logproto"
	"github.com/grafana/loki/pkg/util"
)

const (
	queryPath       = "/loki/api/v1/query?query=%s&limit=%d&time=%d&direction=%s"
	queryRangePath  = "/loki/api/v1/query_range"
	labelsPath      = "/loki/api/v1/labels"
	labelValuesPath = "/loki/api/v1/label/%s/values"
	tailPath        = "/loki/api/v1/tail?query=%s&delay_for=%d&limit=%d&start=%d"
)

// Client contains fields necessary to query a Loki instance
type Client struct {
	TLSConfig config.TLSConfig
	Username  string
	Password  string
	Address   string
	OrgID     string
}

// Query uses the /api/v1/query endpoint to execute an instant query
// excluding interfacer b/c it suggests taking the interface promql.Node instead of logproto.Direction b/c it happens to have a String() method
// nolint:interfacer
func (c *Client) Query(queryStr string, limit int, time time.Time, direction logproto.Direction, quiet bool) (*loghttp.QueryResponse, error) {
	path := fmt.Sprintf(queryPath,
		url.QueryEscape(queryStr), // query
		limit,                     // limit
		time.UnixNano(),           // start
		direction.String(),        // direction
	)

	return c.doQuery(path, quiet)
}

// QueryRange uses the /api/v1/query_range endpoint to execute a range query
// excluding interfacer b/c it suggests taking the interface promql.Node instead of logproto.Direction b/c it happens to have a String() method
// nolint:interfacer
func (c *Client) QueryRange(queryStr string, limit int, from, through time.Time, direction logproto.Direction, step time.Duration, quiet bool) (*loghttp.QueryResponse, error) {
	params := util.NewQueryStringBuilder()
	params.SetString("query", queryStr)
	params.SetInt32("limit", limit)
	params.SetInt("start", from.UnixNano())
	params.SetInt("end", through.UnixNano())
	params.SetString("direction", direction.String())

	// The step is optional, so we do set it only if provided,
	// otherwise we do leverage on the API defaults
	if step != 0 {
		params.SetInt("step", int64(step.Seconds()))
	}

	return c.doQuery(params.EncodeWithPath(queryRangePath), quiet)
}

// ListLabelNames uses the /api/v1/label endpoint to list label names
func (c *Client) ListLabelNames(quiet bool) (*loghttp.LabelResponse, error) {
	var labelResponse loghttp.LabelResponse
	if err := c.doRequest(labelsPath, quiet, &labelResponse); err != nil {
		return nil, err
	}
	return &labelResponse, nil
}

// ListLabelValues uses the /api/v1/label endpoint to list label values
func (c *Client) ListLabelValues(name string, quiet bool) (*loghttp.LabelResponse, error) {
	path := fmt.Sprintf(labelValuesPath, url.PathEscape(name))
	var labelResponse loghttp.LabelResponse
	if err := c.doRequest(path, quiet, &labelResponse); err != nil {
		return nil, err
	}
	return &labelResponse, nil
}

func (c *Client) doQuery(path string, quiet bool) (*loghttp.QueryResponse, error) {
	var err error
	var r loghttp.QueryResponse

	if err = c.doRequest(path, quiet, &r); err != nil {
		return nil, err
	}

	return &r, nil
}

func (c *Client) doRequest(path string, quiet bool, out interface{}) error {
	us := c.Address + path
	if !quiet {
		log.Print(us)
	}

	req, err := http.NewRequest("GET", us, nil)
	if err != nil {
		return err
	}

	req.SetBasicAuth(c.Username, c.Password)

	if c.OrgID != "" {
		req.Header.Set("X-Scope-OrgID", c.OrgID)
	}

	// Parse the URL to extract the host
	clientConfig := config.HTTPClientConfig{
		TLSConfig: c.TLSConfig,
	}

	client, err := config.NewClientFromConfig(clientConfig, "logcli", false)
	if err != nil {
		return err
	}

	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer func() {
		if err := resp.Body.Close(); err != nil {
			log.Println("error closing body", err)
		}
	}()

	if resp.StatusCode/100 != 2 {
		buf, _ := ioutil.ReadAll(resp.Body) // nolint
		return fmt.Errorf("Error response from server: %s (%v)", string(buf), err)
	}

	return json.NewDecoder(resp.Body).Decode(out)
}

// LiveTailQueryConn uses /api/prom/tail to set up a websocket connection and returns it
func (c *Client) LiveTailQueryConn(queryStr string, delayFor int, limit int, from int64, quiet bool) (*websocket.Conn, error) {
	path := fmt.Sprintf(tailPath,
		url.QueryEscape(queryStr), // query
		delayFor,                  // delay_for
		limit,                     // limit
		from,                      // start
	)
	return c.wsConnect(path, quiet)
}

func (c *Client) wsConnect(path string, quiet bool) (*websocket.Conn, error) {
	us := c.Address + path

	tlsConfig, err := config.NewTLSConfig(&c.TLSConfig)
	if err != nil {
		return nil, err
	}

	if strings.HasPrefix(us, "https") {
		us = strings.Replace(us, "https", "wss", 1)
	} else if strings.HasPrefix(us, "http") {
		us = strings.Replace(us, "http", "ws", 1)
	}
	if !quiet {
		log.Println(us)
	}

	h := http.Header{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte(c.Username+":"+c.Password))}}

	if c.OrgID != "" {
		h.Set("X-Scope-OrgID", c.OrgID)
	}

	ws := websocket.Dialer{
		TLSClientConfig: tlsConfig,
	}

	conn, resp, err := ws.Dial(us, h)

	if err != nil {
		if resp == nil {
			return nil, err
		}
		buf, _ := ioutil.ReadAll(resp.Body) // nolint
		return nil, fmt.Errorf("Error response from server: %s (%v)", string(buf), err)
	}

	return conn, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors All rights reserved.

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 proxy

import (
	"fmt"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/golang/glog"
	"github.com/pkg/errors"
	"k8s.io/client-go/rest"
)

// EnvVars are variables we plumb through to the underlying container runtime
var EnvVars = []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"}

// isInBlock checks if ip is a CIDR block
func isInBlock(ip string, block string) (bool, error) {
	if ip == "" {
		return false, fmt.Errorf("ip is nil")
	}
	if block == "" {
		return false, fmt.Errorf("CIDR is nil")
	}

	if ip == block {
		return true, nil
	}

	i := net.ParseIP(ip)
	if i == nil {
		return false, fmt.Errorf("parsed IP is nil")
	}

	// check the block if it's CIDR
	if strings.Contains(block, "/") {
		_, b, err := net.ParseCIDR(block)
		if err != nil {
			return false, errors.Wrapf(err, "Error Parsing block %s", b)
		}

		if b.Contains(i) {
			return true, nil
		}
	}

	return false, errors.New("Error ip not in block")
}

// ExcludeIP takes ip or CIDR as string and excludes it from the http(s)_proxy
func ExcludeIP(ip string) error {
	if netIP := net.ParseIP(ip); netIP == nil {
		if _, _, err := net.ParseCIDR(ip); err != nil {
			return fmt.Errorf("ExcludeIP(%v) requires IP or CIDR as a parameter", ip)
		}
	}
	return updateEnv(ip, "NO_PROXY")
}

// IsIPExcluded checks if an IP is excluded from http(s)_proxy
func IsIPExcluded(ip string) bool {
	return checkEnv(ip, "NO_PROXY")
}

// updateEnv appends an ip to the environment variable
func updateEnv(ip string, env string) error {
	if ip == "" {
		return fmt.Errorf("IP %s is blank. ", ip)
	}
	if !isValidEnv(env) {
		return fmt.Errorf("%s is not a valid env var name for proxy settings", env)
	}
	if !checkEnv(ip, env) {
		v := os.Getenv(env)
		if v == "" {
			return os.Setenv(env, ip)
		}
		return os.Setenv(env, fmt.Sprintf("%s,%s", v, ip))
	}
	return nil
}

// checkEnv checks if ip in an environment variable
func checkEnv(ip string, env string) bool {
	v := os.Getenv(env)
	if v == "" {
		return false
	}
	//  Checking for IP explicitly, i.e., 192.168.39.224
	if strings.Contains(v, ip) {
		return true
	}
	// Checks if included in IP ranges, i.e., 192.168.39.13/24
	noProxyBlocks := strings.Split(v, ",")
	for _, b := range noProxyBlocks {
		yes, err := isInBlock(ip, b)
		if err != nil {
			glog.Errorf("fail to check proxy env: %v", err)
		}
		if yes {
			return true
		}
	}

	return false
}

// isValidEnv checks if the env for proxy settings
func isValidEnv(env string) bool {
	for _, e := range EnvVars {
		if e == env {
			return true
		}
	}
	return false
}

// UpdateTransport takes a k8s client *rest.config and returns a config without a proxy.
func UpdateTransport(cfg *rest.Config) *rest.Config {
	wt := cfg.WrapTransport // Config might already have a transport wrapper
	cfg.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
		if wt != nil {
			rt = wt(rt)
		}
		if ht, ok := rt.(*http.Transport); ok {
			ht.Proxy = nil
			rt = ht
		} else {
			glog.Errorf("Error while casting RoundTripper (of type %T) to *http.Transport : %v", rt, ok)
		}
		return rt
	}
	return cfg
}
<commit_msg>change log level from error to warning<commit_after>/*
Copyright 2019 The Kubernetes Authors All rights reserved.

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 proxy

import (
	"fmt"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/golang/glog"
	"github.com/pkg/errors"
	"k8s.io/client-go/rest"
)

// EnvVars are variables we plumb through to the underlying container runtime
var EnvVars = []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"}

// isInBlock checks if ip is a CIDR block
func isInBlock(ip string, block string) (bool, error) {
	if ip == "" {
		return false, fmt.Errorf("ip is nil")
	}
	if block == "" {
		return false, fmt.Errorf("CIDR is nil")
	}

	if ip == block {
		return true, nil
	}

	i := net.ParseIP(ip)
	if i == nil {
		return false, fmt.Errorf("parsed IP is nil")
	}

	// check the block if it's CIDR
	if strings.Contains(block, "/") {
		_, b, err := net.ParseCIDR(block)
		if err != nil {
			return false, errors.Wrapf(err, "Error Parsing block %s", b)
		}

		if b.Contains(i) {
			return true, nil
		}
	}

	return false, errors.New("Error ip not in block")
}

// ExcludeIP takes ip or CIDR as string and excludes it from the http(s)_proxy
func ExcludeIP(ip string) error {
	if netIP := net.ParseIP(ip); netIP == nil {
		if _, _, err := net.ParseCIDR(ip); err != nil {
			return fmt.Errorf("ExcludeIP(%v) requires IP or CIDR as a parameter", ip)
		}
	}
	return updateEnv(ip, "NO_PROXY")
}

// IsIPExcluded checks if an IP is excluded from http(s)_proxy
func IsIPExcluded(ip string) bool {
	return checkEnv(ip, "NO_PROXY")
}

// updateEnv appends an ip to the environment variable
func updateEnv(ip string, env string) error {
	if ip == "" {
		return fmt.Errorf("IP %s is blank. ", ip)
	}
	if !isValidEnv(env) {
		return fmt.Errorf("%s is not a valid env var name for proxy settings", env)
	}
	if !checkEnv(ip, env) {
		v := os.Getenv(env)
		if v == "" {
			return os.Setenv(env, ip)
		}
		return os.Setenv(env, fmt.Sprintf("%s,%s", v, ip))
	}
	return nil
}

// checkEnv checks if ip in an environment variable
func checkEnv(ip string, env string) bool {
	v := os.Getenv(env)
	if v == "" {
		return false
	}
	//  Checking for IP explicitly, i.e., 192.168.39.224
	if strings.Contains(v, ip) {
		return true
	}
	// Checks if included in IP ranges, i.e., 192.168.39.13/24
	noProxyBlocks := strings.Split(v, ",")
	for _, b := range noProxyBlocks {
		yes, err := isInBlock(ip, b)
		if err != nil {
			glog.Warningf("fail to check proxy env: %v", err)
		}
		if yes {
			return true
		}
	}

	return false
}

// isValidEnv checks if the env for proxy settings
func isValidEnv(env string) bool {
	for _, e := range EnvVars {
		if e == env {
			return true
		}
	}
	return false
}

// UpdateTransport takes a k8s client *rest.config and returns a config without a proxy.
func UpdateTransport(cfg *rest.Config) *rest.Config {
	wt := cfg.WrapTransport // Config might already have a transport wrapper
	cfg.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
		if wt != nil {
			rt = wt(rt)
		}
		if ht, ok := rt.(*http.Transport); ok {
			ht.Proxy = nil
			rt = ht
		} else {
			glog.Errorf("Error while casting RoundTripper (of type %T) to *http.Transport : %v", rt, ok)
		}
		return rt
	}
	return cfg
}
<|endoftext|>


<commit_before>// Copyright 2017-2019 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package agent

import (
	"context"
	"fmt"
	"net"
	"os"

	"github.com/cilium/cilium/pkg/api"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
)

var (
	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "monitor-agent")
)

// buildServer opens a listener socket at path. It exits with logging on all
// errors.
func buildServer(path string) (net.Listener, error) {
	os.Remove(path)
	server, err := net.Listen("unix", path)
	if err != nil {
		return nil, fmt.Errorf("cannot listen on unix socket %s: %s", path, err)
	}

	if os.Getuid() == 0 {
		err := api.SetDefaultPermissions(path)
		if err != nil {
			return nil, fmt.Errorf("cannot set default permissions on socket %s: %s", path, err)
		}
	}

	return server, nil
}

// server serves the Cilium monitor API on the unix domain socket
type server struct {
	listener net.Listener
	monitor  *Agent
}

// ServeMonitorAPI serves the Cilium 1.2 monitor API on a unix domain socket.
// This method starts the server in the background. The server is stopped when
// monitor.Context() is cancelled. Each incoming connection registers a new
// listener on monitor.
func ServeMonitorAPI(monitor *Agent) error {
	listener, err := buildServer(defaults.MonitorSockPath1_2)
	if err != nil {
		return err
	}

	s := &server{
		listener: listener,
		monitor:  monitor,
	}

	log.Infof("Serving cilium node monitor v1.2 API at unix://%s", defaults.MonitorSockPath1_2)

	go s.connectionHandler1_2(monitor.Context())

	return nil
}

// connectionHandler1_2 handles all the incoming connections and sets up the
// listener objects. It will block until ctx is cancelled.
func (s *server) connectionHandler1_2(ctx context.Context) {
	go func() {
		<-ctx.Done()
		s.listener.Close()
	}()

	for !isCtxDone(ctx) {
		conn, err := s.listener.Accept()
		switch {
		case isCtxDone(ctx):
			if conn != nil {
				conn.Close()
			}
			return
		case err != nil:
			log.WithError(err).Warn("Error accepting connection")
			continue
		}

		newListener := newListenerv1_2(conn, option.Config.MonitorQueueSize, s.monitor.RemoveListener)
		s.monitor.RegisterNewListener(newListener)
	}
}
<commit_msg>monitor: use net.UnixListener for unix sockets<commit_after>// Copyright 2017-2019 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package agent

import (
	"context"
	"fmt"
	"net"
	"os"

	"github.com/cilium/cilium/pkg/api"
	"github.com/cilium/cilium/pkg/defaults"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
)

var (
	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "monitor-agent")
)

// buildServer opens a listener socket at path. It exits with logging on all
// errors.
func buildServer(path string) (*net.UnixListener, error) {
	addr, err := net.ResolveUnixAddr("unix", path)
	if err != nil {
		return nil, fmt.Errorf("cannot resolve unix address %s: %s", path, err)
	}
	os.Remove(path)
	server, err := net.ListenUnix("unix", addr)
	if err != nil {
		return nil, fmt.Errorf("cannot listen on unix socket %s: %s", path, err)
	}

	if os.Getuid() == 0 {
		err := api.SetDefaultPermissions(path)
		if err != nil {
			server.Close()
			return nil, fmt.Errorf("cannot set default permissions on socket %s: %s", path, err)
		}
	}

	return server, nil
}

// server serves the Cilium monitor API on the unix domain socket
type server struct {
	listener net.Listener
	monitor  *Agent
}

// ServeMonitorAPI serves the Cilium 1.2 monitor API on a unix domain socket.
// This method starts the server in the background. The server is stopped when
// monitor.Context() is cancelled. Each incoming connection registers a new
// listener on monitor.
func ServeMonitorAPI(monitor *Agent) error {
	listener, err := buildServer(defaults.MonitorSockPath1_2)
	if err != nil {
		return err
	}

	s := &server{
		listener: listener,
		monitor:  monitor,
	}

	log.Infof("Serving cilium node monitor v1.2 API at unix://%s", defaults.MonitorSockPath1_2)

	go s.connectionHandler1_2(monitor.Context())

	return nil
}

// connectionHandler1_2 handles all the incoming connections and sets up the
// listener objects. It will block until ctx is cancelled.
func (s *server) connectionHandler1_2(ctx context.Context) {
	go func() {
		<-ctx.Done()
		s.listener.Close()
	}()

	for !isCtxDone(ctx) {
		conn, err := s.listener.Accept()
		switch {
		case isCtxDone(ctx):
			if conn != nil {
				conn.Close()
			}
			return
		case err != nil:
			log.WithError(err).Warn("Error accepting connection")
			continue
		}

		newListener := newListenerv1_2(conn, option.Config.MonitorQueueSize, s.monitor.RemoveListener)
		s.monitor.RegisterNewListener(newListener)
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Wandoujia Inc. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package router

import (
	"sync"
	"time"

	"github.com/wandoulabs/codis/pkg/proxy/redis"
	"github.com/wandoulabs/codis/pkg/utils/errors"
	"github.com/wandoulabs/codis/pkg/utils/log"
)

type BackendConn struct {
	addr string
	stop sync.Once

	input chan *Request
}

func NewBackendConn(addr string) *BackendConn {
	bc := &BackendConn{
		addr:  addr,
		input: make(chan *Request, 1024),
	}
	go bc.Run()
	return bc
}

func (bc *BackendConn) Run() {
	log.Infof("backend conn [%p] to %s, start service", bc, bc.addr)
	for k := 0; ; k++ {
		err := bc.loopWriter()
		if err == nil {
			break
		}
		log.WarnErrorf(err, "backend conn [%p] to %s, restart [%d]", bc, bc.addr, k)
		time.Sleep(time.Millisecond * 50)
	}
	log.Infof("backend conn [%p] to %s, stop and exit", bc, bc.addr)
}

func (bc *BackendConn) Addr() string {
	return bc.addr
}

func (bc *BackendConn) Close() {
	bc.stop.Do(func() {
		close(bc.input)
	})
}

func (bc *BackendConn) PushBack(r *Request) {
	bc.input <- r
}

func (bc *BackendConn) KeepAlive() bool {
	if len(bc.input) != 0 {
		return false
	}
	r := &Request{
		Wait: &sync.WaitGroup{},
		Resp: redis.NewArray([]*redis.Resp{
			redis.NewBulkBytes([]byte("PING")),
		}),
	}
	r.Wait.Add(1)

	select {
	case bc.input <- r:
		return true
	default:
		return false
	}
}

var ErrZombieRequest = errors.New("request from zombie session")

func (bc *BackendConn) loopWriter() error {
	r, ok := <-bc.input
	if ok {
		c, tasks, err := bc.newBackendReader()
		if err != nil {
			return bc.setResponse(r, nil, err)
		}
		defer close(tasks)

		p := &FlushPolicy{
			Encoder:     c.Writer,
			MaxBuffered: 64,
			MaxInterval: 300,
		}
		for ok {
			var flush = len(bc.input) == 0
			if bc.canForward(r) {
				if err := p.Encode(r.Resp, flush); err != nil {
					return bc.setResponse(r, nil, err)
				}
				tasks <- r
			} else {
				if err := p.Flush(flush); err != nil {
					return bc.setResponse(r, nil, err)
				}
				bc.setResponse(r, nil, ErrZombieRequest)
			}

			r, ok = <-bc.input
		}
	}
	return nil
}

func (bc *BackendConn) newBackendReader() (*redis.Conn, chan<- *Request, error) {
	c, err := redis.DialTimeout(bc.addr, 1024*512, time.Second)
	if err != nil {
		return nil, nil, err
	}
	c.ReaderTimeout = time.Minute
	c.WriterTimeout = time.Minute

	tasks := make(chan *Request, 4096)
	go func() {
		defer c.Close()
		for r := range tasks {
			resp, err := c.Reader.Decode()
			bc.setResponse(r, resp, err)
		}
	}()
	return c, tasks, nil
}

func (bc *BackendConn) canForward(r *Request) bool {
	return r.Owner == nil || !r.Owner.IsZombie()
}

func (bc *BackendConn) setResponse(r *Request, resp *redis.Resp, err error) error {
	r.Response.Resp, r.Response.Err = resp, err
	if s := r.slot; s != nil {
		s.jobs.Done()
	}
	if err != nil && r.Owner != nil {
		r.Owner.MarkZombie()
	}
	r.Wait.Done()
	return err
}

type SharedBackendConn struct {
	*BackendConn
	mu sync.Mutex

	refcnt int
}

func NewSharedBackendConn(addr string) *SharedBackendConn {
	return &SharedBackendConn{BackendConn: NewBackendConn(addr), refcnt: 1}
}

func (s *SharedBackendConn) Close() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.refcnt <= 0 {
		log.Panicf("shared backend conn has been closed, close too many times")
	}
	if s.refcnt == 1 {
		s.BackendConn.Close()
	}
	s.refcnt--
	return s.refcnt == 0
}

func (s *SharedBackendConn) IncrRefcnt() {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.refcnt == 0 {
		log.Panicf("shared backend conn has been closed")
	}
	s.refcnt++
}

type FlushPolicy struct {
	*redis.Encoder

	MaxBuffered int
	MaxInterval int64

	nbuffered int
	lastflush int64
}

func (p *FlushPolicy) needFlush() bool {
	if p.nbuffered != 0 {
		if p.nbuffered > p.MaxBuffered {
			return true
		}
		if microseconds()-p.lastflush > p.MaxInterval {
			return true
		}
	}
	return false
}

func (p *FlushPolicy) Flush(force bool) error {
	if force || p.needFlush() {
		if err := p.Encoder.Flush(); err != nil {
			return err
		}
		p.nbuffered = 0
		p.lastflush = microseconds()
	}
	return nil
}

func (p *FlushPolicy) Encode(resp *redis.Resp, force bool) error {
	if err := p.Encoder.Encode(resp, false); err != nil {
		return err
	} else {
		p.nbuffered++
		return p.Flush(force)
	}
}
<commit_msg>backend conn discard all following requests if error happens<commit_after>// Copyright 2014 Wandoujia Inc. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package router

import (
	"sync"
	"time"

	"github.com/wandoulabs/codis/pkg/proxy/redis"
	"github.com/wandoulabs/codis/pkg/utils/errors"
	"github.com/wandoulabs/codis/pkg/utils/log"
)

type BackendConn struct {
	addr string
	stop sync.Once

	input chan *Request
}

func NewBackendConn(addr string) *BackendConn {
	bc := &BackendConn{
		addr:  addr,
		input: make(chan *Request, 1024),
	}
	go bc.Run()
	return bc
}

func (bc *BackendConn) Run() {
	log.Infof("backend conn [%p] to %s, start service", bc, bc.addr)
	for k := 0; ; k++ {
		err := bc.loopWriter()
		if err == nil {
			break
		} else {
			for i := len(bc.input); i != 0; i-- {
				r := <-bc.input
				bc.setResponse(r, nil, err)
			}
		}
		log.WarnErrorf(err, "backend conn [%p] to %s, restart [%d]", bc, bc.addr, k)
		time.Sleep(time.Millisecond * 50)
	}
	log.Infof("backend conn [%p] to %s, stop and exit", bc, bc.addr)
}

func (bc *BackendConn) Addr() string {
	return bc.addr
}

func (bc *BackendConn) Close() {
	bc.stop.Do(func() {
		close(bc.input)
	})
}

func (bc *BackendConn) PushBack(r *Request) {
	bc.input <- r
}

func (bc *BackendConn) KeepAlive() bool {
	if len(bc.input) != 0 {
		return false
	}
	r := &Request{
		Wait: &sync.WaitGroup{},
		Resp: redis.NewArray([]*redis.Resp{
			redis.NewBulkBytes([]byte("PING")),
		}),
	}
	r.Wait.Add(1)

	select {
	case bc.input <- r:
		return true
	default:
		return false
	}
}

var ErrZombieRequest = errors.New("request from zombie session")

func (bc *BackendConn) loopWriter() error {
	r, ok := <-bc.input
	if ok {
		c, tasks, err := bc.newBackendReader()
		if err != nil {
			return bc.setResponse(r, nil, err)
		}
		defer close(tasks)

		p := &FlushPolicy{
			Encoder:     c.Writer,
			MaxBuffered: 64,
			MaxInterval: 300,
		}
		for ok {
			var flush = len(bc.input) == 0
			if bc.canForward(r) {
				if err := p.Encode(r.Resp, flush); err != nil {
					return bc.setResponse(r, nil, err)
				}
				tasks <- r
			} else {
				if err := p.Flush(flush); err != nil {
					return bc.setResponse(r, nil, err)
				}
				bc.setResponse(r, nil, ErrZombieRequest)
			}

			r, ok = <-bc.input
		}
	}
	return nil
}

func (bc *BackendConn) newBackendReader() (*redis.Conn, chan<- *Request, error) {
	c, err := redis.DialTimeout(bc.addr, 1024*512, time.Second)
	if err != nil {
		return nil, nil, err
	}
	c.ReaderTimeout = time.Minute
	c.WriterTimeout = time.Minute

	tasks := make(chan *Request, 4096)
	go func() {
		defer c.Close()
		for r := range tasks {
			resp, err := c.Reader.Decode()
			bc.setResponse(r, resp, err)
		}
	}()
	return c, tasks, nil
}

func (bc *BackendConn) canForward(r *Request) bool {
	return r.Owner == nil || !r.Owner.IsZombie()
}

func (bc *BackendConn) setResponse(r *Request, resp *redis.Resp, err error) error {
	r.Response.Resp, r.Response.Err = resp, err
	if s := r.slot; s != nil {
		s.jobs.Done()
	}
	if err != nil && r.Owner != nil {
		r.Owner.MarkZombie()
	}
	r.Wait.Done()
	return err
}

type SharedBackendConn struct {
	*BackendConn
	mu sync.Mutex

	refcnt int
}

func NewSharedBackendConn(addr string) *SharedBackendConn {
	return &SharedBackendConn{BackendConn: NewBackendConn(addr), refcnt: 1}
}

func (s *SharedBackendConn) Close() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.refcnt <= 0 {
		log.Panicf("shared backend conn has been closed, close too many times")
	}
	if s.refcnt == 1 {
		s.BackendConn.Close()
	}
	s.refcnt--
	return s.refcnt == 0
}

func (s *SharedBackendConn) IncrRefcnt() {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.refcnt == 0 {
		log.Panicf("shared backend conn has been closed")
	}
	s.refcnt++
}

type FlushPolicy struct {
	*redis.Encoder

	MaxBuffered int
	MaxInterval int64

	nbuffered int
	lastflush int64
}

func (p *FlushPolicy) needFlush() bool {
	if p.nbuffered != 0 {
		if p.nbuffered > p.MaxBuffered {
			return true
		}
		if microseconds()-p.lastflush > p.MaxInterval {
			return true
		}
	}
	return false
}

func (p *FlushPolicy) Flush(force bool) error {
	if force || p.needFlush() {
		if err := p.Encoder.Flush(); err != nil {
			return err
		}
		p.nbuffered = 0
		p.lastflush = microseconds()
	}
	return nil
}

func (p *FlushPolicy) Encode(resp *redis.Resp, force bool) error {
	if err := p.Encoder.Encode(resp, false); err != nil {
		return err
	} else {
		p.nbuffered++
		return p.Flush(force)
	}
}
<|endoftext|>