<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 test

import (
	"crypto"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	fakeclock "k8s.io/utils/clock/testing"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	"github.com/jetstack/cert-manager/pkg/controller/certificates"
	"github.com/jetstack/cert-manager/pkg/util/pki"
	"github.com/jetstack/cert-manager/test/unit/gen"
)

var (
	certificateGvk = cmapi.SchemeGroupVersion.WithKind("Certificate")
)

type CryptoBundle struct {
	// certificate is the Certificate resource used to create this bundle
	Certificate *cmapi.Certificate
	// expectedRequestName is the name of the CertificateRequest that is
	// expected to be created to issue this certificate
	ExpectedRequestName string

	// privateKey is the private key used as the complement to the certificates
	// in this bundle
	PrivateKey      crypto.Signer
	PrivateKeyBytes []byte

	// csr is the CSR used to obtain the certificate in this bundle
	CSR      *x509.CertificateRequest
	CSRBytes []byte

	// certificateRequest is the request that is expected to be created to
	// obtain a certificate when using this bundle
	CertificateRequest                     *cmapi.CertificateRequest
	CertificateRequestPending              *cmapi.CertificateRequest
	CertificateRequestReady                *cmapi.CertificateRequest
	CertificateRequestFailed               *cmapi.CertificateRequest
	CertificateRequestFailedInvalidRequest *cmapi.CertificateRequest

	// cert is a signed certificate
	Cert      *x509.Certificate
	CertBytes []byte

	LocalTemporaryCertificateBytes []byte

	FixedClock *fakeclock.FakeClock
}

func MustCreateCryptoBundle(t *testing.T, crt *cmapi.Certificate, fixedClock *fakeclock.FakeClock) CryptoBundle {
	c, err := createCryptoBundle(crt, fixedClock)
	if err != nil {
		t.Fatalf("error generating crypto bundle: %v", err)
	}
	return *c
}

func createCryptoBundle(originalCert *cmapi.Certificate, fixedClock *fakeclock.FakeClock) (*CryptoBundle, error) {
	crt := originalCert.DeepCopy()
	if crt.Spec.PrivateKey == nil {
		crt.Spec.PrivateKey = &cmapi.CertificatePrivateKey{}
	}
	reqName, err := apiutil.ComputeName(crt.Name, crt.Spec)
	if err != nil {
		return nil, err
	}

	privateKey, err := pki.GeneratePrivateKeyForCertificate(crt)
	if err != nil {
		return nil, err
	}

	privateKeyBytes, err := pki.EncodePrivateKey(privateKey, crt.Spec.PrivateKey.Encoding)
	if err != nil {
		return nil, err
	}

	csrPEM, err := generateCSRImpl(crt, privateKeyBytes)
	if err != nil {
		return nil, err
	}

	csr, err := pki.DecodeX509CertificateRequestBytes(csrPEM)
	if err != nil {
		return nil, err
	}

	annotations := make(map[string]string)
	for k, v := range crt.Annotations {
		annotations[k] = v
	}
	if crt.Status.Revision != nil {
		annotations[cmapi.CertificateRequestRevisionAnnotationKey] = fmt.Sprintf("%d", crt.Status.Revision)
	}

	annotations[cmapi.CertificateRequestPrivateKeyAnnotationKey] = crt.Spec.SecretName
	annotations[cmapi.CertificateNameKey] = crt.Name
	certificateRequest := &cmapi.CertificateRequest{
		ObjectMeta: metav1.ObjectMeta{
			Name:            reqName,
			Namespace:       crt.Namespace,
			OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(crt, certificateGvk)},
			Annotations:     annotations,
		},
		Spec: cmapi.CertificateRequestSpec{
			Request:   csrPEM,
			Duration:  crt.Spec.Duration,
			IssuerRef: crt.Spec.IssuerRef,
			IsCA:      crt.Spec.IsCA,
		},
	}

	unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest)
	if err != nil {
		return nil, err
	}

	certBytes, cert, err := pki.SignCertificate(unsignedCert, unsignedCert, privateKey.Public(), privateKey)
	if err != nil {
		return nil, err
	}

	certificateRequestReady := gen.CertificateRequestFrom(certificateRequest,
		gen.SetCertificateRequestCertificate(certBytes),
		gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
			Type:   cmapi.CertificateRequestConditionReady,
			Status: cmmeta.ConditionTrue,
			Reason: cmapi.CertificateRequestReasonIssued,
		}),
	)

	certificateRequestPending := gen.CertificateRequestFrom(certificateRequest,
		gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
			Type:   cmapi.CertificateRequestConditionReady,
			Status: cmmeta.ConditionFalse,
			Reason: cmapi.CertificateRequestReasonPending,
		}),
	)

	certificateRequestFailed := gen.CertificateRequestFrom(certificateRequest,
		gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
			Type:   cmapi.CertificateRequestConditionReady,
			Status: cmmeta.ConditionFalse,
			Reason: cmapi.CertificateRequestReasonFailed,
		}),
	)

	certificateRequestFailedInvalidRequest := gen.CertificateRequestFrom(certificateRequestFailed,
		gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
			Type:   cmapi.CertificateRequestConditionInvalidRequest,
			Status: cmmeta.ConditionTrue,
			Reason: cmapi.CertificateRequestReasonFailed,
		}),
	)

	tempCertBytes, err := certificates.GenerateLocallySignedTemporaryCertificate(crt, privateKeyBytes)
	if err != nil {
		panic("failed to generate test fixture: " + err.Error())
	}

	return &CryptoBundle{
		Certificate:                            originalCert,
		ExpectedRequestName:                    reqName,
		PrivateKey:                             privateKey,
		PrivateKeyBytes:                        privateKeyBytes,
		CSR:                                    csr,
		CSRBytes:                               csrPEM,
		CertificateRequest:                     certificateRequest,
		CertificateRequestPending:              certificateRequestPending,
		CertificateRequestReady:                certificateRequestReady,
		CertificateRequestFailed:               certificateRequestFailed,
		CertificateRequestFailedInvalidRequest: certificateRequestFailedInvalidRequest,
		Cert:                                   cert,
		CertBytes:                              certBytes,
		LocalTemporaryCertificateBytes:         tempCertBytes,
		FixedClock:                             fixedClock,
	}, nil
}

func generateCSRImpl(crt *cmapi.Certificate, pk []byte) ([]byte, error) {
	csr, err := pki.GenerateCSR(crt)
	if err != nil {
		return nil, err
	}

	signer, err := pki.DecodePrivateKeyBytes(pk)
	if err != nil {
		return nil, err
	}

	csrDER, err := pki.EncodeCSR(csr, signer)
	if err != nil {
		return nil, err
	}

	csrPEM := pem.EncodeToMemory(&pem.Block{
		Type: "CERTIFICATE REQUEST", Bytes: csrDER,
	})

	return csrPEM, nil
}

// MustGenerateCSRImpl returns PEM encoded certificate signing request
func MustGenerateCSRImpl(t *testing.T, pkData []byte, cert *cmapi.Certificate) []byte {
	csrPEM, err := generateCSRImpl(cert, pkData)
	if err != nil {
		t.Fatal(err)
	}
	return csrPEM
}

// MustCreatePEMPrivateKey returns a PEM encoded 2048 bit RSA private key
func MustCreatePEMPrivateKey(t *testing.T) []byte {
	pk, err := pki.GenerateRSAPrivateKey(2048)
	if err != nil {
		t.Fatal(err)
	}
	pkData, err := pki.EncodePrivateKey(pk, cmapi.PKCS8)
	if err != nil {
		t.Fatal(err)
	}
	return pkData
}

// MustCreateCertWithNotBeforeAfter returns a self-signed x509 cert for Certificate
// with the provided NotBefore, NotAfter values
func MustCreateCertWithNotBeforeAfter(t *testing.T, pkData []byte, spec *cmapi.Certificate, notBefore, notAfter time.Time) []byte {
	pk, err := pki.DecodePrivateKeyBytes(pkData)
	if err != nil {
		t.Fatal(err)
	}

	template, err := pki.GenerateTemplate(spec)
	if err != nil {
		t.Fatal(err)
	}

	template.NotBefore = notBefore
	template.NotAfter = notAfter

	certData, _, err := pki.SignCertificate(template, template, pk.Public(), pk)
	if err != nil {
		t.Fatal(err)
	}

	return certData
}

// MustCreateCert returns a self-signed x509 certificate
func MustCreateCert(t *testing.T, pkData []byte, spec *cmapi.Certificate) []byte {
	pk, err := pki.DecodePrivateKeyBytes(pkData)
	if err != nil {
		t.Fatal(err)
	}

	template, err := pki.GenerateTemplate(spec)
	if err != nil {
		t.Fatal(err)
	}

	certData, _, err := pki.SignCertificate(template, template, pk.Public(), pk)
	if err != nil {
		t.Fatal(err)
	}

	return certData
}
<commit_msg><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 test

import (
	"crypto"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	fakeclock "k8s.io/utils/clock/testing"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	"github.com/jetstack/cert-manager/pkg/controller/certificates"
	"github.com/jetstack/cert-manager/pkg/util/pki"
	"github.com/jetstack/cert-manager/test/unit/gen"
)

var (
	certificateGvk = cmapi.SchemeGroupVersion.WithKind("Certificate")
)

type CryptoBundle struct {
	// certificate is the Certificate resource used to create this bundle
	Certificate *cmapi.Certificate
	// expectedRequestName is the name of the CertificateRequest that is
	// expected to be created to issue this certificate
	ExpectedRequestName string

	// privateKey is the private key used as the complement to the certificates
	// in this bundle
	PrivateKey      crypto.Signer
	PrivateKeyBytes []byte

	// csr is the CSR used to obtain the certificate in this bundle
	CSR      *x509.CertificateRequest
	CSRBytes []byte

	// certificateRequest is the request that is expected to be created to
	// obtain a certificate when using this bundle
	CertificateRequest                     *cmapi.CertificateRequest
	CertificateRequestPending              *cmapi.CertificateRequest
	CertificateRequestReady                *cmapi.CertificateRequest
	CertificateRequestFailed               *cmapi.CertificateRequest
	CertificateRequestFailedInvalidRequest *cmapi.CertificateRequest

	// cert is a signed certificate
	Cert      *x509.Certificate
	CertBytes []byte

	LocalTemporaryCertificateBytes []byte

	FixedClock *fakeclock.FakeClock
}

func MustCreateCryptoBundle(t *testing.T, crt *cmapi.Certificate, fixedClock *fakeclock.FakeClock) CryptoBundle {
	c, err := createCryptoBundle(crt, fixedClock)
	if err != nil {
		t.Fatalf("error generating crypto bundle: %v", err)
	}
	return *c
}

func createCryptoBundle(originalCert *cmapi.Certificate, fixedClock *fakeclock.FakeClock) (*CryptoBundle, error) {
	crt := originalCert.DeepCopy()
	if crt.Spec.PrivateKey == nil {
		crt.Spec.PrivateKey = &cmapi.CertificatePrivateKey{}
	}
	reqName, err := apiutil.ComputeName(crt.Name, crt.Spec)
	if err != nil {
		return nil, err
	}

	privateKey, err := pki.GeneratePrivateKeyForCertificate(crt)
	if err != nil {
		return nil, err
	}

	privateKeyBytes, err := pki.EncodePrivateKey(privateKey, crt.Spec.PrivateKey.Encoding)
	if err != nil {
		return nil, err
	}

	csrPEM, err := generateCSRImpl(crt, privateKeyBytes)
	if err != nil {
		return nil, err
	}

	csr, err := pki.DecodeX509CertificateRequestBytes(csrPEM)
	if err != nil {
		return nil, err
	}

	annotations := make(map[string]string)
	for k, v := range crt.Annotations {
		annotations[k] = v
	}
	if crt.Status.Revision != nil {
		annotations[cmapi.CertificateRequestRevisionAnnotationKey] = fmt.Sprintf("%d", *crt.Status.Revision)
	}

	annotations[cmapi.CertificateRequestPrivateKeyAnnotationKey] = crt.Spec.SecretName
	annotations[cmapi.CertificateNameKey] = crt.Name
	certificateRequest := &cmapi.CertificateRequest{
		ObjectMeta: metav1.ObjectMeta{
			Name:            reqName,
			Namespace:       crt.Namespace,
			OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(crt, certificateGvk)},
			Annotations:     annotations,
		},
		Spec: cmapi.CertificateRequestSpec{
			Request:   csrPEM,
			Duration:  crt.Spec.Duration,
			IssuerRef: crt.Spec.IssuerRef,
			IsCA:      crt.Spec.IsCA,
		},
	}

	unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest)
	if err != nil {
		return nil, err
	}

	certBytes, cert, err := pki.SignCertificate(unsignedCert, unsignedCert, privateKey.Public(), privateKey)
	if err != nil {
		return nil, err
	}

	certificateRequestReady := gen.CertificateRequestFrom(certificateRequest,
		gen.SetCertificateRequestCertificate(certBytes),
		gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
			Type:   cmapi.CertificateRequestConditionReady,
			Status: cmmeta.ConditionTrue,
			Reason: cmapi.CertificateRequestReasonIssued,
		}),
	)

	certificateRequestPending := gen.CertificateRequestFrom(certificateRequest,
		gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
			Type:   cmapi.CertificateRequestConditionReady,
			Status: cmmeta.ConditionFalse,
			Reason: cmapi.CertificateRequestReasonPending,
		}),
	)

	certificateRequestFailed := gen.CertificateRequestFrom(certificateRequest,
		gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
			Type:   cmapi.CertificateRequestConditionReady,
			Status: cmmeta.ConditionFalse,
			Reason: cmapi.CertificateRequestReasonFailed,
		}),
	)

	certificateRequestFailedInvalidRequest := gen.CertificateRequestFrom(certificateRequestFailed,
		gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
			Type:   cmapi.CertificateRequestConditionInvalidRequest,
			Status: cmmeta.ConditionTrue,
			Reason: cmapi.CertificateRequestReasonFailed,
		}),
	)

	tempCertBytes, err := certificates.GenerateLocallySignedTemporaryCertificate(crt, privateKeyBytes)
	if err != nil {
		panic("failed to generate test fixture: " + err.Error())
	}

	return &CryptoBundle{
		Certificate:                            originalCert,
		ExpectedRequestName:                    reqName,
		PrivateKey:                             privateKey,
		PrivateKeyBytes:                        privateKeyBytes,
		CSR:                                    csr,
		CSRBytes:                               csrPEM,
		CertificateRequest:                     certificateRequest,
		CertificateRequestPending:              certificateRequestPending,
		CertificateRequestReady:                certificateRequestReady,
		CertificateRequestFailed:               certificateRequestFailed,
		CertificateRequestFailedInvalidRequest: certificateRequestFailedInvalidRequest,
		Cert:                                   cert,
		CertBytes:                              certBytes,
		LocalTemporaryCertificateBytes:         tempCertBytes,
		FixedClock:                             fixedClock,
	}, nil
}

func generateCSRImpl(crt *cmapi.Certificate, pk []byte) ([]byte, error) {
	csr, err := pki.GenerateCSR(crt)
	if err != nil {
		return nil, err
	}

	signer, err := pki.DecodePrivateKeyBytes(pk)
	if err != nil {
		return nil, err
	}

	csrDER, err := pki.EncodeCSR(csr, signer)
	if err != nil {
		return nil, err
	}

	csrPEM := pem.EncodeToMemory(&pem.Block{
		Type: "CERTIFICATE REQUEST", Bytes: csrDER,
	})

	return csrPEM, nil
}

// MustGenerateCSRImpl returns PEM encoded certificate signing request
func MustGenerateCSRImpl(t *testing.T, pkData []byte, cert *cmapi.Certificate) []byte {
	csrPEM, err := generateCSRImpl(cert, pkData)
	if err != nil {
		t.Fatal(err)
	}
	return csrPEM
}

// MustCreatePEMPrivateKey returns a PEM encoded 2048 bit RSA private key
func MustCreatePEMPrivateKey(t *testing.T) []byte {
	pk, err := pki.GenerateRSAPrivateKey(2048)
	if err != nil {
		t.Fatal(err)
	}
	pkData, err := pki.EncodePrivateKey(pk, cmapi.PKCS8)
	if err != nil {
		t.Fatal(err)
	}
	return pkData
}

// MustCreateCertWithNotBeforeAfter returns a self-signed x509 cert for Certificate
// with the provided NotBefore, NotAfter values
func MustCreateCertWithNotBeforeAfter(t *testing.T, pkData []byte, spec *cmapi.Certificate, notBefore, notAfter time.Time) []byte {
	pk, err := pki.DecodePrivateKeyBytes(pkData)
	if err != nil {
		t.Fatal(err)
	}

	template, err := pki.GenerateTemplate(spec)
	if err != nil {
		t.Fatal(err)
	}

	template.NotBefore = notBefore
	template.NotAfter = notAfter

	certData, _, err := pki.SignCertificate(template, template, pk.Public(), pk)
	if err != nil {
		t.Fatal(err)
	}

	return certData
}

// MustCreateCert returns a self-signed x509 certificate
func MustCreateCert(t *testing.T, pkData []byte, spec *cmapi.Certificate) []byte {
	pk, err := pki.DecodePrivateKeyBytes(pkData)
	if err != nil {
		t.Fatal(err)
	}

	template, err := pki.GenerateTemplate(spec)
	if err != nil {
		t.Fatal(err)
	}

	certData, _, err := pki.SignCertificate(template, template, pk.Public(), pk)
	if err != nil {
		t.Fatal(err)
	}

	return certData
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 bsutil

import (
	"testing"

	"github.com/blang/semver/v4"
)

func TestVersionIsBetween(t *testing.T) {
	tests := []struct {
		description string
		ver         semver.Version
		gte         semver.Version
		lte         semver.Version
		expected    bool
	}{
		{
			description: "between",
			ver:         semver.MustParse("1.8.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    true,
		},
		{
			description: "less than minimum version",
			ver:         semver.MustParse("1.6.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    false,
		},
		{
			description: "greater than max version",
			ver:         semver.MustParse("2.8.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    true,
		},
		{
			description: "equal to max version",
			ver:         semver.MustParse("1.9.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    true,
		},
		{
			description: "equal to min version",
			ver:         semver.MustParse("1.7.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    true,
		},
		{
			description: "alpha between",
			ver:         semver.MustParse("1.8.0-alpha.0"),
			gte:         semver.MustParse("1.8.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    true,
		},
		{
			description: "beta greater than alpha",
			ver:         semver.MustParse("1.8.0-beta.1"),
			gte:         semver.MustParse("1.8.0"),
			lte:         semver.MustParse("1.8.0-alpha.0"),
			expected:    false,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			t.Parallel()
			between := versionIsBetween(test.ver, test.gte, test.lte)
			if between != test.expected {
				t.Errorf("Expected: %t, Actual: %t", test.expected, between)
			}
		})
	}
}
<commit_msg>Fix wrong use of loop variable in parallel test<commit_after>/*
Copyright 2016 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 bsutil

import (
	"testing"

	"github.com/blang/semver/v4"
)

func TestVersionIsBetween(t *testing.T) {
	tests := []struct {
		description string
		ver         semver.Version
		gte         semver.Version
		lte         semver.Version
		expected    bool
	}{
		{
			description: "between",
			ver:         semver.MustParse("1.8.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    true,
		},
		{
			description: "less than minimum version",
			ver:         semver.MustParse("1.6.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    false,
		},
		{
			description: "greater than max version",
			ver:         semver.MustParse("2.8.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    false,
		},
		{
			description: "equal to max version",
			ver:         semver.MustParse("1.9.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    true,
		},
		{
			description: "equal to min version",
			ver:         semver.MustParse("1.7.0"),
			gte:         semver.MustParse("1.7.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    true,
		},
		{
			description: "alpha before",
			ver:         semver.MustParse("1.8.0-alpha.0"),
			gte:         semver.MustParse("1.8.0"),
			lte:         semver.MustParse("1.9.0"),
			expected:    false,
		},
		{
			description: "beta greater than alpha",
			ver:         semver.MustParse("1.8.0-beta.1"),
			gte:         semver.MustParse("1.8.0-alpha.0"),
			lte:         semver.MustParse("1.8.0"),
			expected:    true,
		},
	}

	for _, test := range tests {
		test := test // capture range variable
		t.Run(test.description, func(t *testing.T) {
			t.Parallel()
			between := versionIsBetween(test.ver, test.gte, test.lte)
			if between != test.expected {
				t.Errorf("Expected: %t, Actual: %t", test.expected, between)
			}
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 kubeadm

import (
	"testing"

	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/util"
)

func TestGenerateConfig(t *testing.T) {
	tests := []struct {
		description string
		cfg         config.KubernetesConfig
		expectedCfg string
		shouldErr   bool
	}{
		{
			description: "no extra args",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.100",
				KubernetesVersion: "v1.10.0",
				NodeName:          "minikube",
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.100
  bindPort: 8443
kubernetesVersion: v1.10.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
`,
		},
		{
			description: "extra args all components",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.10.0-alpha.0",
				NodeName:          "extra-args-minikube",
				ExtraOptions: util.ExtraOptionSlice{
					util.ExtraOption{
						Component: Apiserver,
						Key:       "fail-no-swap",
						Value:     "true",
					},
					util.ExtraOption{
						Component: ControllerManager,
						Key:       "kube-api-burst",
						Value:     "32",
					},
					util.ExtraOption{
						Component: Scheduler,
						Key:       "scheduler-name",
						Value:     "mini-scheduler",
					},
				},
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.101
  bindPort: 8443
kubernetesVersion: v1.10.0-alpha.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: extra-args-minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
  fail-no-swap: "true"
controllerManagerExtraArgs:
  kube-api-burst: "32"
schedulerExtraArgs:
  scheduler-name: "mini-scheduler"
`,
		},
		{
			description: "two extra args for one component",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.10.0-alpha.0",
				NodeName:          "extra-args-minikube",
				ExtraOptions: util.ExtraOptionSlice{
					util.ExtraOption{
						Component: Apiserver,
						Key:       "fail-no-swap",
						Value:     "true",
					},
					util.ExtraOption{
						Component: Apiserver,
						Key:       "kube-api-burst",
						Value:     "32",
					},
				},
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.101
  bindPort: 8443
kubernetesVersion: v1.10.0-alpha.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: extra-args-minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
  fail-no-swap: "true"
  kube-api-burst: "32"
`,
		},
		{
			description: "enable feature gates",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.10.0-alpha.0",
				NodeName:          "extra-args-minikube",
				FeatureGates:      "HugePages=true,OtherFeature=false",
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.101
  bindPort: 8443
kubernetesVersion: v1.10.0-alpha.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: extra-args-minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
  feature-gates: "HugePages=true,OtherFeature=false"
controllerManagerExtraArgs:
  feature-gates: "HugePages=true,OtherFeature=false"
schedulerExtraArgs:
  feature-gates: "HugePages=true,OtherFeature=false"
`,
		},
		{
			description: "enable feature gates and extra config",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.10.0-alpha.0",
				NodeName:          "extra-args-minikube",
				FeatureGates:      "HugePages=true,OtherFeature=false",
				ExtraOptions: util.ExtraOptionSlice{
					util.ExtraOption{
						Component: Apiserver,
						Key:       "fail-no-swap",
						Value:     "true",
					},
				},
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.101
  bindPort: 8443
kubernetesVersion: v1.10.0-alpha.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: extra-args-minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
  fail-no-swap: "true"
  feature-gates: "HugePages=true,OtherFeature=false"
controllerManagerExtraArgs:
  feature-gates: "HugePages=true,OtherFeature=false"
schedulerExtraArgs:
  feature-gates: "HugePages=true,OtherFeature=false"
`,
		},
		{
			// Unknown components should fail silently
			description: "unknown component",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.8.0-alpha.0",
				NodeName:          "extra-args-minikube",
				ExtraOptions: util.ExtraOptionSlice{
					util.ExtraOption{
						Component: "not-a-real-component",
						Key:       "killswitch",
						Value:     "true",
					},
				},
			},
			shouldErr: true,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			actualCfg, err := generateConfig(test.cfg)
			if err != nil && !test.shouldErr {
				t.Errorf("got unexpected error generating config: %s", err)
				return
			}
			if err == nil && test.shouldErr {
				t.Errorf("expected error but got none, config: %s", actualCfg)
				return
			}
			if actualCfg != test.expectedCfg {
				t.Errorf("actual config does not match expected.  actual:\n%sexpected:\n%s", actualCfg, test.expectedCfg)
				return
			}
		})
	}
}
<commit_msg>Add missing controlPlaneEndpoint to fix kubeadm templates test<commit_after>/*
Copyright 2016 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 kubeadm

import (
	"testing"

	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/util"
)

func TestGenerateConfig(t *testing.T) {
	tests := []struct {
		description string
		cfg         config.KubernetesConfig
		expectedCfg string
		shouldErr   bool
	}{
		{
			description: "no extra args",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.100",
				KubernetesVersion: "v1.10.0",
				NodeName:          "minikube",
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.100
  bindPort: 8443
  controlPlaneEndpoint: localhost
kubernetesVersion: v1.10.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
`,
		},
		{
			description: "extra args all components",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.10.0-alpha.0",
				NodeName:          "extra-args-minikube",
				ExtraOptions: util.ExtraOptionSlice{
					util.ExtraOption{
						Component: Apiserver,
						Key:       "fail-no-swap",
						Value:     "true",
					},
					util.ExtraOption{
						Component: ControllerManager,
						Key:       "kube-api-burst",
						Value:     "32",
					},
					util.ExtraOption{
						Component: Scheduler,
						Key:       "scheduler-name",
						Value:     "mini-scheduler",
					},
				},
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.101
  bindPort: 8443
  controlPlaneEndpoint: localhost
kubernetesVersion: v1.10.0-alpha.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: extra-args-minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
  fail-no-swap: "true"
controllerManagerExtraArgs:
  kube-api-burst: "32"
schedulerExtraArgs:
  scheduler-name: "mini-scheduler"
`,
		},
		{
			description: "two extra args for one component",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.10.0-alpha.0",
				NodeName:          "extra-args-minikube",
				ExtraOptions: util.ExtraOptionSlice{
					util.ExtraOption{
						Component: Apiserver,
						Key:       "fail-no-swap",
						Value:     "true",
					},
					util.ExtraOption{
						Component: Apiserver,
						Key:       "kube-api-burst",
						Value:     "32",
					},
				},
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.101
  bindPort: 8443
  controlPlaneEndpoint: localhost
kubernetesVersion: v1.10.0-alpha.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: extra-args-minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
  fail-no-swap: "true"
  kube-api-burst: "32"
`,
		},
		{
			description: "enable feature gates",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.10.0-alpha.0",
				NodeName:          "extra-args-minikube",
				FeatureGates:      "HugePages=true,OtherFeature=false",
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.101
  bindPort: 8443
  controlPlaneEndpoint: localhost
kubernetesVersion: v1.10.0-alpha.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: extra-args-minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
  feature-gates: "HugePages=true,OtherFeature=false"
controllerManagerExtraArgs:
  feature-gates: "HugePages=true,OtherFeature=false"
schedulerExtraArgs:
  feature-gates: "HugePages=true,OtherFeature=false"
`,
		},
		{
			description: "enable feature gates and extra config",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.10.0-alpha.0",
				NodeName:          "extra-args-minikube",
				FeatureGates:      "HugePages=true,OtherFeature=false",
				ExtraOptions: util.ExtraOptionSlice{
					util.ExtraOption{
						Component: Apiserver,
						Key:       "fail-no-swap",
						Value:     "true",
					},
				},
			},
			expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
  advertiseAddress: 192.168.1.101
  bindPort: 8443
  controlPlaneEndpoint: localhost
kubernetesVersion: v1.10.0-alpha.0
certificatesDir: /var/lib/localkube/certs/
networking:
  serviceSubnet: 10.96.0.0/12
etcd:
  dataDir: /data/minikube
nodeName: extra-args-minikube
apiServerExtraArgs:
  admission-control: "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota"
  fail-no-swap: "true"
  feature-gates: "HugePages=true,OtherFeature=false"
controllerManagerExtraArgs:
  feature-gates: "HugePages=true,OtherFeature=false"
schedulerExtraArgs:
  feature-gates: "HugePages=true,OtherFeature=false"
`,
		},
		{
			// Unknown components should fail silently
			description: "unknown component",
			cfg: config.KubernetesConfig{
				NodeIP:            "192.168.1.101",
				KubernetesVersion: "v1.8.0-alpha.0",
				NodeName:          "extra-args-minikube",
				ExtraOptions: util.ExtraOptionSlice{
					util.ExtraOption{
						Component: "not-a-real-component",
						Key:       "killswitch",
						Value:     "true",
					},
				},
			},
			shouldErr: true,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			actualCfg, err := generateConfig(test.cfg)
			if err != nil && !test.shouldErr {
				t.Errorf("got unexpected error generating config: %s", err)
				return
			}
			if err == nil && test.shouldErr {
				t.Errorf("expected error but got none, config: %s", actualCfg)
				return
			}
			if actualCfg != test.expectedCfg {
				t.Errorf("actual config does not match expected.  actual:\n%sexpected:\n%s", actualCfg, test.expectedCfg)
				return
			}
		})
	}
}
<|endoftext|>


<commit_before>//go:build test_integration && test_local

/*
Copyright 2017 The Nuclio 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 test

import (
	"encoding/json"
	"net/http"
	"path"
	"testing"
	"time"

	"github.com/nuclio/nuclio/pkg/processor/trigger/http/test/suite"

	"github.com/stretchr/testify/suite"
)

var (
	requestHeaders = map[string]interface{}{
		"Content-Type": "application/json",
	}
)

type timeoutSuite struct {
	httpsuite.TestSuite
}

type timeoutResponse struct {
	PID int `json:"pid"`
}

func (suite *timeoutSuite) SetupTest() {
	suite.TestSuite.SetupTest()

	suite.Runtime = "python"
	suite.RuntimeDir = "python"
	suite.FunctionDir = path.Join(suite.GetNuclioSourceDir(), "pkg", "processor", "runtime", "python", "test")
}

func (suite *timeoutSuite) TestTimeout() {
	createFunctionOptions := suite.GetDeployOptions("timeout",
		path.Join(suite.GetTestFunctionsDir(), "python", "timeout"))

	timeout := 100 * time.Millisecond
	createFunctionOptions.FunctionConfig.Spec.EventTimeout = timeout.String()
	createFunctionOptions.FunctionConfig.Spec.Handler = "timeout:handler"
	var oldPID int
	okStatusCode := http.StatusOK
	timeoutStatusCode := http.StatusRequestTimeout
	sleepTime := 1 * time.Second

	suite.DeployFunctionAndRequests(createFunctionOptions, []*httpsuite.Request{
		{
			RequestBody:    suite.genTimeoutRequest(time.Millisecond),
			RequestHeaders: requestHeaders,

			ExpectedResponseBody: func(body []byte) {
				response := &timeoutResponse{}
				err := json.Unmarshal(body, response)
				suite.Require().NoErrorf(err, "Can't parse response - %q", string(body))
				oldPID = response.PID
			},
			ExpectedResponseStatusCode: &okStatusCode,
		},
		{
			RequestBody:    suite.genTimeoutRequest(sleepTime),
			RequestHeaders: requestHeaders,

			ExpectedResponseStatusCode: &timeoutStatusCode,
		},

		// retry until runtime is back
		{
			RequestBody:    suite.genTimeoutRequest(0),
			RequestHeaders: requestHeaders,

			RetryUntilSuccessfulStatusCode: &okStatusCode,
			RetryUntilSuccessfulInterval:   1,
			RetryUntilSuccessfulDuration:   2 * sleepTime,
		},
		{
			RequestBody:    suite.genTimeoutRequest(time.Millisecond),
			RequestHeaders: requestHeaders,

			ExpectedResponseBody: func(body []byte) {
				response := &timeoutResponse{}
				err := json.Unmarshal(body, response)
				suite.Require().NoErrorf(err, "Can't parse response - %q", string(body))
				suite.Require().NotEqual(oldPID, response.PID, "Wrapper PID didn't change")
			},
			ExpectedResponseStatusCode: &okStatusCode,
		},
	})
}

func (suite *timeoutSuite) genTimeoutRequest(timeout time.Duration) string {
	request := map[string]interface{}{
		"timeout": timeout.String(),
	}
	data, err := json.Marshal(request)
	suite.Require().NoErrorf(err, "Can't encode request - %#v", request)

	return string(data)
}

func TestTimeout(t *testing.T) {
	if testing.Short() {
		return
	}

	suite.Run(t, &timeoutSuite{})
}
<commit_msg>TestTimeout - increase timeout and sleep time (#2633)<commit_after>//go:build test_integration && test_local

/*
Copyright 2017 The Nuclio 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 test

import (
	"encoding/json"
	"net/http"
	"path"
	"testing"
	"time"

	"github.com/nuclio/nuclio/pkg/processor/trigger/http/test/suite"

	"github.com/stretchr/testify/suite"
)

var (
	requestHeaders = map[string]interface{}{
		"Content-Type": "application/json",
	}
)

type timeoutSuite struct {
	httpsuite.TestSuite
}

type timeoutResponse struct {
	PID int `json:"pid"`
}

func (suite *timeoutSuite) SetupTest() {
	suite.TestSuite.SetupTest()

	suite.Runtime = "python"
	suite.RuntimeDir = "python"
	suite.FunctionDir = path.Join(suite.GetNuclioSourceDir(), "pkg", "processor", "runtime", "python", "test")
}

func (suite *timeoutSuite) TestTimeout() {
	createFunctionOptions := suite.GetDeployOptions("timeout",
		path.Join(suite.GetTestFunctionsDir(), "python", "timeout"))

	timeout := 500 * time.Millisecond
	createFunctionOptions.FunctionConfig.Spec.EventTimeout = timeout.String()
	createFunctionOptions.FunctionConfig.Spec.Handler = "timeout:handler"
	var oldPID int
	okStatusCode := http.StatusOK
	timeoutStatusCode := http.StatusRequestTimeout
	sleepTime := 2 * time.Second

	suite.DeployFunctionAndRequests(createFunctionOptions, []*httpsuite.Request{
		{
			RequestBody:    suite.genTimeoutRequest(time.Millisecond),
			RequestHeaders: requestHeaders,

			ExpectedResponseBody: func(body []byte) {
				response := &timeoutResponse{}
				err := json.Unmarshal(body, response)
				suite.Require().NoErrorf(err, "Can't parse response - %q", string(body))
				oldPID = response.PID
			},
			ExpectedResponseStatusCode: &okStatusCode,
		},
		{
			RequestBody:    suite.genTimeoutRequest(sleepTime),
			RequestHeaders: requestHeaders,

			ExpectedResponseStatusCode: &timeoutStatusCode,
		},

		// retry until runtime is back
		{
			RequestBody:    suite.genTimeoutRequest(0),
			RequestHeaders: requestHeaders,

			RetryUntilSuccessfulStatusCode: &okStatusCode,
			RetryUntilSuccessfulInterval:   10 * time.Millisecond,
			RetryUntilSuccessfulDuration:   2 * sleepTime,
		},
		{
			RequestBody:    suite.genTimeoutRequest(time.Millisecond),
			RequestHeaders: requestHeaders,

			ExpectedResponseBody: func(body []byte) {
				response := &timeoutResponse{}
				err := json.Unmarshal(body, response)
				suite.Require().NoErrorf(err, "Can't parse response - %q", string(body))
				suite.Require().NotEqual(oldPID, response.PID, "Wrapper PID didn't change")
			},
			ExpectedResponseStatusCode: &okStatusCode,
		},
	})
}

func (suite *timeoutSuite) genTimeoutRequest(timeout time.Duration) string {
	request := map[string]interface{}{
		"timeout": timeout.String(),
	}
	data, err := json.Marshal(request)
	suite.Require().NoErrorf(err, "Can't encode request - %#v", request)

	return string(data)
}

func TestTimeout(t *testing.T) {
	if testing.Short() {
		return
	}

	suite.Run(t, &timeoutSuite{})
}
<|endoftext|>


<commit_before>package lib

// all supported options of ossutil
const (
	OptionConfigFile       string = "configFile"
	OptionEndpoint                = "endpoint"
	OptionAccessKeyID             = "accessKeyID"
	OptionAccessKeySecret         = "accessKeySecret"
	OptionSTSToken                = "stsToken"
    OptionACL                     = "acl"
	OptionShortFormat             = "shortFormat"
	OptionDirectory               = "directory"
	OptionRecursion               = "recursive"
	OptionBucket                  = "bucket"
	OptionForce                   = "force"
	OptionUpdate                  = "update"
	OptionDelete                  = "delete"
	OptionBigFileThreshold        = "bigfileThreshold"
	OptionCheckpointDir           = "checkpointDir"
	OptionRetryTimes              = "retryTimes"
	OptionRoutines                = "routines"
    OptionLanguage                = "language"
	OptionVersion                 = "version"
	OptionMan                     = "man"
)

// the elements show in stat object
const (
    StatName                string = "Name"
    StatLocation                   = "Location"
    StatCreationDate               = "CreationDate"
    StatExtranetEndpoint           = "ExtranetEndpoint"
    StatIntranetEndpoint           = "IntranetEndpoint"
    StatACL                        = "ACL"
    StatOwner                      = "Owner"
)

const (
    updateEndpoint      string = "oss-test.aliyun-inc.com"
    updateBucket               = "ossutilupdate"
    updateVersionObject        = "ossutilversion"
    updateBinaryLinux          = "ossutil"
    updateBinaryWindow32       = "ossutil32.exe"
    updateBinaryWindow64       = "ossutil64.exe"
    updateBinaryMac64          = "ossutilmac64"
    updateTmpVersionFile       = ".ossutil_tmp_vsersion"
)

// global public variable
const (
	Package             string = "ossutil"
	ChannelBuf          int    = 1000
	Version             string = "1.0.0.Beta"
	DefaultEndpoint     string = "oss-cn-hangzhou.aliyuncs.com"
	DefaultLanguage            = "中文"
    EnglishLanguage            = "English"
	Scheme              string = "oss"
    DefaultConfigFile          = "~/.ossutilconfig"
	MaxUint                    = ^uint(0)
	MaxInt                     = int(MaxUint >> 1)
	MaxUint64                  = ^uint64(0)
	MaxInt64                   = int64(MaxUint64 >> 1)
	CheckpointDir              = ".ossutil_checkpoint"
	CheckpointSep              = "---"
	MaxPartNum                 = 10000
	MaxIdealPartNum            = MaxPartNum / 20
	MinIdealPartNum            = MaxPartNum / 500
	MaxIdealPartSize           = 524288000
	MinIdealPartSize           = 10485760
	BigFileThreshold           = 524288000
	MaxBigFileThreshold        = MaxInt64
	MinBigFileThreshold        = 0
	RetryTimes          int    = 3
	MaxRetryTimes       int64  = 500
	MinRetryTimes       int64  = 1
	Routines            int    = 3
	MaxRoutines         int64  = 32
	MinRoutines         int64  = 1
)
<commit_msg>modify update bucket name<commit_after>package lib

// all supported options of ossutil
const (
	OptionConfigFile       string = "configFile"
	OptionEndpoint                = "endpoint"
	OptionAccessKeyID             = "accessKeyID"
	OptionAccessKeySecret         = "accessKeySecret"
	OptionSTSToken                = "stsToken"
    OptionACL                     = "acl"
	OptionShortFormat             = "shortFormat"
	OptionDirectory               = "directory"
	OptionRecursion               = "recursive"
	OptionBucket                  = "bucket"
	OptionForce                   = "force"
	OptionUpdate                  = "update"
	OptionDelete                  = "delete"
	OptionBigFileThreshold        = "bigfileThreshold"
	OptionCheckpointDir           = "checkpointDir"
	OptionRetryTimes              = "retryTimes"
	OptionRoutines                = "routines"
    OptionLanguage                = "language"
	OptionVersion                 = "version"
	OptionMan                     = "man"
)

// the elements show in stat object
const (
    StatName                string = "Name"
    StatLocation                   = "Location"
    StatCreationDate               = "CreationDate"
    StatExtranetEndpoint           = "ExtranetEndpoint"
    StatIntranetEndpoint           = "IntranetEndpoint"
    StatACL                        = "ACL"
    StatOwner                      = "Owner"
)

const (
    updateEndpoint      string = "oss-cn-hangzhou.aliyuncs.com"
    updateBucket               = "ossutil-version-update"
    updateVersionObject        = "ossutilversion"
    updateBinaryLinux          = "ossutil"
    updateBinaryWindow32       = "ossutil32.exe"
    updateBinaryWindow64       = "ossutil64.exe"
    updateBinaryMac64          = "ossutilmac64"
    updateTmpVersionFile       = ".ossutil_tmp_vsersion"
)

// global public variable
const (
	Package             string = "ossutil"
	ChannelBuf          int    = 1000
	Version             string = "1.0.0.Beta"
	DefaultEndpoint     string = "oss-cn-hangzhou.aliyuncs.com"
	DefaultLanguage            = "中文"
    EnglishLanguage            = "English"
	Scheme              string = "oss"
    DefaultConfigFile          = "~/.ossutilconfig"
	MaxUint                    = ^uint(0)
	MaxInt                     = int(MaxUint >> 1)
	MaxUint64                  = ^uint64(0)
	MaxInt64                   = int64(MaxUint64 >> 1)
	CheckpointDir              = ".ossutil_checkpoint"
	CheckpointSep              = "---"
	MaxPartNum                 = 10000
	MaxIdealPartNum            = MaxPartNum / 20
	MinIdealPartNum            = MaxPartNum / 500
	MaxIdealPartSize           = 524288000
	MinIdealPartSize           = 10485760
	BigFileThreshold           = 524288000
	MaxBigFileThreshold        = MaxInt64
	MinBigFileThreshold        = 0
	RetryTimes          int    = 3
	MaxRetryTimes       int64  = 500
	MinRetryTimes       int64  = 1
	Routines            int    = 3
	MaxRoutines         int64  = 32
	MinRoutines         int64  = 1
)
<|endoftext|>


<commit_before>package val

import (
	"bytes"
	"context"
	"database/sql"
	"github.com/ghts/ghts/lib"
	"strconv"
	"time"
)

func F내재가치_정보_모음_DB읽기(db *sql.DB) (s *S내재가치_정보_모음, 에러 error) {
	s = New내재가치_정보_모음()
	에러 = s.DB읽기(db)

	return s, 에러
}

func New내재가치_정보_모음() *S내재가치_정보_모음 {
	s := new(S내재가치_정보_모음)
	s.M저장소 = make(map[string]*S내재가치_정보)

	return s
}

type S내재가치_정보_모음 struct {
	M저장소 map[string]*S내재가치_정보
}

func (s *S내재가치_정보_모음) G종목별_최신_정보(종목코드 string) *S내재가치_정보 {
	for 연도 := lib.F지금().Year(); 연도 > 2015; 연도-- {
		for 월 := 12; 월 > 0; 월-- {
			키 := 종목코드 + lib.F2문자열(연도*100+월)
			if 값, 존재함 := s.M저장소[키]; 존재함 {
				return 값
			}
		}
	}

	return nil
}

func (s *S내재가치_정보_모음) G종목별_차최신_정보(종목코드 string) *S내재가치_정보 {
	차최신_연도 := s.G종목별_최신_정보(종목코드).S내재가치_식별정보.G일자2().Year() - 1
	for 연도 := 차최신_연도; 연도 > 2015; 연도-- {
		for 월 := 12; 월 > 0; 월-- {
			키 := 종목코드 + lib.F2문자열(연도*100+월)
			if 값, 존재함 := s.M저장소[키]; 존재함 {
				return 값
			}
		}
	}

	return nil
}

func (s *S내재가치_정보_모음) G종목별_차차최신_정보(종목코드 string) *S내재가치_정보 {
	차차최신_연도 := s.G종목별_최신_정보(종목코드).S내재가치_식별정보.G일자2().Year() - 2
	for 연도 := 차차최신_연도; 연도 > 2015; 연도-- {
		for 월 := 12; 월 > 0; 월-- {
			키 := 종목코드 + lib.F2문자열(연도*100+월)
			if 값, 존재함 := s.M저장소[키]; 존재함 {
				return 값
			}
		}
	}

	return nil
}

func (s *S내재가치_정보_모음) S상장주식수_업데이트(db *sql.DB, 종목코드 string, 수량 int64) error {
	if 값 := s.G종목별_최신_정보(종목코드); 값 != nil {
		값.M상장주식수 = float64(수량)
		return F내재가치_정보_모음_DB저장(db, []*S내재가치_정보{값})
	}

	return nil
}

func (s *S내재가치_정보_모음) S파일_읽기(파일명 string) error {
	return lib.JSON_파일_읽기(파일명, s)
}

func (s *S내재가치_정보_모음) S파일_저장(파일명 string) error {
	return lib.JSON_파일_저장(s, 파일명)
}

func (s *S내재가치_정보_모음) DB읽기(db *sql.DB) (에러 error) {
	defer lib.S예외처리{M에러: &에러, M함수: func() { s = nil }}.S실행()

	lib.F확인(F내재가치_정보_테이블_생성(db))

	SQL := new(bytes.Buffer)
	SQL.WriteString("SELECT code, date, json ")
	SQL.WriteString("FROM fundamental_data ")
	SQL.WriteString("ORDER BY code, date")

	stmt, 에러 := db.Prepare(SQL.String())
	lib.F확인(에러)
	defer stmt.Close()

	rows, 에러 := stmt.Query()
	lib.F확인(에러)
	defer rows.Close()

	for rows.Next() {
		var code string
		var date time.Time
		var json string
		var 값 *S내재가치_정보

		lib.F확인(rows.Scan(&code, &date, &json))

		if 에러 = lib.F디코딩(lib.JSON, []byte(json), &값); 에러 != nil {
			lib.F문자열_출력("%v %v : 디코딩 에러\n%v", code, date.Format(lib.P일자_형식), 에러)
			continue
		} else if 값 == nil {
			lib.F문자열_출력("%v %v : nil 값", code, date.Format(lib.P일자_형식))
			continue
		} else if 값.S내재가치_식별정보 == nil {
			lib.F문자열_출력("%v %v : nil 식별정보", code, date.Format(lib.P일자_형식))
			continue
		}

		s.M저장소[값.G키()] = 값
	}

	return
}

func (s *S내재가치_정보_모음) DB저장(db *sql.DB) error {
	// 맵 데이터를 슬라이스 형태로 변환
	값_모음 := make([]*S내재가치_정보, len(s.M저장소))

	i := 0
	for _, 값 := range s.M저장소 {
		값_모음[i] = 값
		i++
	}

	// 슬라이스 데이터 DB에 저장
	return F내재가치_정보_모음_DB저장(db, 값_모음)
}

type S내재가치_정보 struct {
	*S내재가치_식별정보
	*S재무제표_정보_내용
	*S재무비율_정보_내용
	*S투자지표_정보_내용
}

type S재무제표_정보 struct {
	*S내재가치_식별정보
	*S재무제표_정보_내용
}

type S재무비율_정보 struct {
	*S내재가치_식별정보
	*S재무비율_정보_내용
}

type S투자지표_정보 struct {
	*S내재가치_식별정보
	*S투자지표_정보_내용
}

func New내재가치_식별정보(종목코드 string, 연도 uint16, 월 uint8) *S내재가치_식별정보 {
	s := new(S내재가치_식별정보)
	s.M종목코드 = 종목코드
	s.S연도_월(연도, 월)

	return s
}

type S내재가치_식별정보 struct {
	M종목코드 string
	M연도_월 uint32
}

func (s S내재가치_식별정보) G키() string {
	return s.M종목코드 + strconv.Itoa(int(s.M연도_월))
}

func (s *S내재가치_식별정보) S연도_월(연도 uint16, 월 uint8) {
	s.M연도_월 = uint32(연도)*100 + uint32(월)
}

func (s S내재가치_식별정보) G일자() uint32 {
	return s.M연도_월*100 + 1
}

func (s S내재가치_식별정보) G일자2() time.Time {
	return lib.F2포맷된_일자_단순형("20060102", strconv.Itoa(int(s.M연도_월*100+1)))
}

type S재무제표_정보_내용 struct {
	M매출액      float64
	M매출총이익    float64
	M영업이익     float64
	M세전계속사업이익 float64
	M당기순이익    float64
	M자산       float64
	M유동자산     float64 // NCAV 전략에 필요함.
	M현금및현금성자산 float64 // EV계산에 필요함.
	M부채       float64
	M유동부채     float64
	M자본       float64
	M영업_현금흐름  float64
	M투자_현금흐름  float64
	M재무_현금흐름  float64
	M현금_증가    float64
}

type S재무비율_정보_내용 struct {
	M유동_비율     float64
	M당좌_비율     float64
	M부채_비율     float64
	M이자보상배율    float64
	M매출총이익율    float64
	M세전계속사업이익율 float64
	M영업이익율     float64
	EBITDA마진율  float64
	ROA        float64
	ROE        float64
	ROIC       float64
	M총자산회전율    float64
	M총부채회전율    float64
	M총자본회전율    float64
	M순운전자본회전율  float64
}

type S투자지표_정보_내용 struct {
	M상장주식수    float64
	EPS       float64
	EBITDAPS  float64
	CFPS      float64
	SPS       float64
	BPS       float64
	DPS       float64
	M배당성향     float64
	M총현금흐름    float64
	M세후영업이익   float64
	M유무형자산상각비 float64
	M총투자      float64
	FCFF      float64
}

func F내재가치_정보_모음_DB저장(db *sql.DB, 값_모음 []*S내재가치_정보) (에러 error) {
	var tx *sql.Tx
	defer lib.S예외처리{M에러: &에러, M함수: func() { lib.F조건부_실행(tx != nil, tx.Rollback) }}.S실행()

	lib.F확인(F내재가치_정보_테이블_생성(db))

	SQL생성 := new(bytes.Buffer)
	SQL생성.WriteString("INSERT IGNORE INTO fundamental_data (")
	SQL생성.WriteString("code,")
	SQL생성.WriteString("date,")
	SQL생성.WriteString("json")
	SQL생성.WriteString(") VALUES (?,?,'')")

	SQL수정 := new(bytes.Buffer)
	SQL수정.WriteString("UPDATE fundamental_data SET json=? ")
	SQL수정.WriteString("WHERE code=? AND date=?")

	txOpts := new(sql.TxOptions)
	txOpts.Isolation = sql.LevelDefault
	txOpts.ReadOnly = false

	tx, 에러 = db.BeginTx(context.TODO(), txOpts)
	lib.F확인(에러)

	stmt생성, 에러 := tx.Prepare(SQL생성.String())
	lib.F확인(에러)
	defer stmt생성.Close()

	stmt수정, 에러 := tx.Prepare(SQL수정.String())
	lib.F확인(에러)
	defer stmt수정.Close()

	for _, 값 := range 값_모음 {
		json, 에러 := lib.F인코딩(lib.JSON, 값)
		lib.F확인(에러)

		_, 에러 = stmt생성.Exec(값.M종목코드, 값.G일자())
		lib.F확인(에러)

		_, 에러 = stmt수정.Exec(string(json), 값.M종목코드, 값.G일자())
		lib.F확인(에러)
	}

	tx.Commit()

	return nil
}

func F내재가치_정보_테이블_생성(db *sql.DB) error {
	SQL := new(bytes.Buffer)
	SQL.WriteString("CREATE TABLE IF NOT EXISTS fundamental_data (")
	SQL.WriteString("code CHAR(8) NOT NULL,")
	SQL.WriteString("date DATE NOT NULL,")
	SQL.WriteString("json TEXT NOT NULL,")
	SQL.WriteString("CONSTRAINT PRIMARY KEY (code,date)")
	SQL.WriteString(")")

	_, 에러 := db.Exec(SQL.String())

	return 에러
}
<commit_msg>val.S재무비율_정보_내용 EBITDA 항목 삭제<commit_after>package val

import (
	"bytes"
	"context"
	"database/sql"
	"github.com/ghts/ghts/lib"
	"strconv"
	"time"
)

func F내재가치_정보_모음_DB읽기(db *sql.DB) (s *S내재가치_정보_모음, 에러 error) {
	s = New내재가치_정보_모음()
	에러 = s.DB읽기(db)

	return s, 에러
}

func New내재가치_정보_모음() *S내재가치_정보_모음 {
	s := new(S내재가치_정보_모음)
	s.M저장소 = make(map[string]*S내재가치_정보)

	return s
}

type S내재가치_정보_모음 struct {
	M저장소 map[string]*S내재가치_정보
}

func (s *S내재가치_정보_모음) G종목별_최신_정보(종목코드 string) *S내재가치_정보 {
	for 연도 := lib.F지금().Year(); 연도 > 2015; 연도-- {
		for 월 := 12; 월 > 0; 월-- {
			키 := 종목코드 + lib.F2문자열(연도*100+월)
			if 값, 존재함 := s.M저장소[키]; 존재함 {
				return 값
			}
		}
	}

	return nil
}

func (s *S내재가치_정보_모음) G종목별_차최신_정보(종목코드 string) *S내재가치_정보 {
	차최신_연도 := s.G종목별_최신_정보(종목코드).S내재가치_식별정보.G일자2().Year() - 1
	for 연도 := 차최신_연도; 연도 > 2015; 연도-- {
		for 월 := 12; 월 > 0; 월-- {
			키 := 종목코드 + lib.F2문자열(연도*100+월)
			if 값, 존재함 := s.M저장소[키]; 존재함 {
				return 값
			}
		}
	}

	return nil
}

func (s *S내재가치_정보_모음) G종목별_차차최신_정보(종목코드 string) *S내재가치_정보 {
	차차최신_연도 := s.G종목별_최신_정보(종목코드).S내재가치_식별정보.G일자2().Year() - 2
	for 연도 := 차차최신_연도; 연도 > 2015; 연도-- {
		for 월 := 12; 월 > 0; 월-- {
			키 := 종목코드 + lib.F2문자열(연도*100+월)
			if 값, 존재함 := s.M저장소[키]; 존재함 {
				return 값
			}
		}
	}

	return nil
}

func (s *S내재가치_정보_모음) S상장주식수_업데이트(db *sql.DB, 종목코드 string, 수량 int64) error {
	if 값 := s.G종목별_최신_정보(종목코드); 값 != nil {
		값.M상장주식수 = float64(수량)
		return F내재가치_정보_모음_DB저장(db, []*S내재가치_정보{값})
	}

	return nil
}

func (s *S내재가치_정보_모음) S파일_읽기(파일명 string) error {
	return lib.JSON_파일_읽기(파일명, s)
}

func (s *S내재가치_정보_모음) S파일_저장(파일명 string) error {
	return lib.JSON_파일_저장(s, 파일명)
}

func (s *S내재가치_정보_모음) DB읽기(db *sql.DB) (에러 error) {
	defer lib.S예외처리{M에러: &에러, M함수: func() { s = nil }}.S실행()

	lib.F확인(F내재가치_정보_테이블_생성(db))

	SQL := new(bytes.Buffer)
	SQL.WriteString("SELECT code, date, json ")
	SQL.WriteString("FROM fundamental_data ")
	SQL.WriteString("ORDER BY code, date")

	stmt, 에러 := db.Prepare(SQL.String())
	lib.F확인(에러)
	defer stmt.Close()

	rows, 에러 := stmt.Query()
	lib.F확인(에러)
	defer rows.Close()

	for rows.Next() {
		var code string
		var date time.Time
		var json string
		var 값 *S내재가치_정보

		lib.F확인(rows.Scan(&code, &date, &json))

		if 에러 = lib.F디코딩(lib.JSON, []byte(json), &값); 에러 != nil {
			lib.F문자열_출력("%v %v : 디코딩 에러\n%v", code, date.Format(lib.P일자_형식), 에러)
			continue
		} else if 값 == nil {
			lib.F문자열_출력("%v %v : nil 값", code, date.Format(lib.P일자_형식))
			continue
		} else if 값.S내재가치_식별정보 == nil {
			lib.F문자열_출력("%v %v : nil 식별정보", code, date.Format(lib.P일자_형식))
			continue
		}

		s.M저장소[값.G키()] = 값
	}

	return
}

func (s *S내재가치_정보_모음) DB저장(db *sql.DB) error {
	// 맵 데이터를 슬라이스 형태로 변환
	값_모음 := make([]*S내재가치_정보, len(s.M저장소))

	i := 0
	for _, 값 := range s.M저장소 {
		값_모음[i] = 값
		i++
	}

	// 슬라이스 데이터 DB에 저장
	return F내재가치_정보_모음_DB저장(db, 값_모음)
}

type S내재가치_정보 struct {
	*S내재가치_식별정보
	*S재무제표_정보_내용
	*S재무비율_정보_내용
	*S투자지표_정보_내용
}

type S재무제표_정보 struct {
	*S내재가치_식별정보
	*S재무제표_정보_내용
}

type S재무비율_정보 struct {
	*S내재가치_식별정보
	*S재무비율_정보_내용
}

type S투자지표_정보 struct {
	*S내재가치_식별정보
	*S투자지표_정보_내용
}

func New내재가치_식별정보(종목코드 string, 연도 uint16, 월 uint8) *S내재가치_식별정보 {
	s := new(S내재가치_식별정보)
	s.M종목코드 = 종목코드
	s.S연도_월(연도, 월)

	return s
}

type S내재가치_식별정보 struct {
	M종목코드 string
	M연도_월 uint32
}

func (s S내재가치_식별정보) G키() string {
	return s.M종목코드 + strconv.Itoa(int(s.M연도_월))
}

func (s *S내재가치_식별정보) S연도_월(연도 uint16, 월 uint8) {
	s.M연도_월 = uint32(연도)*100 + uint32(월)
}

func (s S내재가치_식별정보) G일자() uint32 {
	return s.M연도_월*100 + 1
}

func (s S내재가치_식별정보) G일자2() time.Time {
	return lib.F2포맷된_일자_단순형("20060102", strconv.Itoa(int(s.M연도_월*100+1)))
}

type S재무제표_정보_내용 struct {
	M매출액      float64
	M매출총이익    float64
	M영업이익     float64
	M세전계속사업이익 float64
	M당기순이익    float64
	M자산       float64
	M유동자산     float64 // NCAV 전략에 필요함.
	M현금및현금성자산 float64 // EV계산에 필요함.
	M부채       float64
	M유동부채     float64
	M자본       float64
	M영업_현금흐름  float64
	M투자_현금흐름  float64
	M재무_현금흐름  float64
	M현금_증가    float64
}

type S재무비율_정보_내용 struct {
	M유동_비율     float64
	M당좌_비율     float64
	M부채_비율     float64
	M이자보상배율    float64
	M매출총이익율    float64
	M세전계속사업이익율 float64
	M영업이익율     float64
	//EBITDA마진율  float64	// EBITDA가 왜 싫으세요?" (워런버핏, 찰리멍거) https://youtu.be/7Fze3RRyQTw
	ROA        float64
	ROE        float64
	ROIC       float64
	M총자산회전율    float64
	M총부채회전율    float64
	M총자본회전율    float64
	M순운전자본회전율  float64
}

type S투자지표_정보_내용 struct {
	M상장주식수    float64
	EPS       float64
	//EBITDAPS  float64	// EBITDA가 왜 싫으세요?" (워런버핏, 찰리멍거) https://youtu.be/7Fze3RRyQTw
	CFPS      float64
	SPS       float64
	BPS       float64
	DPS       float64
	M배당성향     float64
	M총현금흐름    float64
	M세후영업이익   float64
	M유무형자산상각비 float64
	M총투자      float64
	FCFF      float64
}

func F내재가치_정보_모음_DB저장(db *sql.DB, 값_모음 []*S내재가치_정보) (에러 error) {
	var tx *sql.Tx
	defer lib.S예외처리{M에러: &에러, M함수: func() { lib.F조건부_실행(tx != nil, tx.Rollback) }}.S실행()

	lib.F확인(F내재가치_정보_테이블_생성(db))

	SQL생성 := new(bytes.Buffer)
	SQL생성.WriteString("INSERT IGNORE INTO fundamental_data (")
	SQL생성.WriteString("code,")
	SQL생성.WriteString("date,")
	SQL생성.WriteString("json")
	SQL생성.WriteString(") VALUES (?,?,'')")

	SQL수정 := new(bytes.Buffer)
	SQL수정.WriteString("UPDATE fundamental_data SET json=? ")
	SQL수정.WriteString("WHERE code=? AND date=?")

	txOpts := new(sql.TxOptions)
	txOpts.Isolation = sql.LevelDefault
	txOpts.ReadOnly = false

	tx, 에러 = db.BeginTx(context.TODO(), txOpts)
	lib.F확인(에러)

	stmt생성, 에러 := tx.Prepare(SQL생성.String())
	lib.F확인(에러)
	defer stmt생성.Close()

	stmt수정, 에러 := tx.Prepare(SQL수정.String())
	lib.F확인(에러)
	defer stmt수정.Close()

	for _, 값 := range 값_모음 {
		json, 에러 := lib.F인코딩(lib.JSON, 값)
		lib.F확인(에러)

		_, 에러 = stmt생성.Exec(값.M종목코드, 값.G일자())
		lib.F확인(에러)

		_, 에러 = stmt수정.Exec(string(json), 값.M종목코드, 값.G일자())
		lib.F확인(에러)
	}

	tx.Commit()

	return nil
}

func F내재가치_정보_테이블_생성(db *sql.DB) error {
	SQL := new(bytes.Buffer)
	SQL.WriteString("CREATE TABLE IF NOT EXISTS fundamental_data (")
	SQL.WriteString("code CHAR(8) NOT NULL,")
	SQL.WriteString("date DATE NOT NULL,")
	SQL.WriteString("json TEXT NOT NULL,")
	SQL.WriteString("CONSTRAINT PRIMARY KEY (code,date)")
	SQL.WriteString(")")

	_, 에러 := db.Exec(SQL.String())

	return 에러
}
<|endoftext|>


<commit_before>package container

import (
	"crypto/tls"
	"fmt"
	"time"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/samalba/dockerclient"
)

const (
	defaultStopSignal = "SIGTERM"
	dryRunPrefix      = "DRY: "
)

// A Filter is a prototype for a function that can be used to filter the
// results from a call to the ListContainers() method on the Client.
type Filter func(Container) bool

// A Client is the interface through which Pumba interacts with the Docker API.
type Client interface {
	ListContainers(Filter) ([]Container, error)
	StopContainer(Container, time.Duration, bool) error
	KillContainer(Container, string, bool) error
	StartContainer(Container) error
	RenameContainer(Container, string) error
	RemoveImage(Container, bool, bool) error
	RemoveContainer(Container, bool, bool) error
	DisruptContainer(Container, string, bool) error
}

// NewClient returns a new Client instance which can be used to interact with
// the Docker API.
func NewClient(dockerHost string, tlsConfig *tls.Config, pullImages bool) Client {
	docker, err := dockerclient.NewDockerClient(dockerHost, tlsConfig)

	if err != nil {
		log.Fatalf("Error instantiating Docker client: %s", err)
	}

	return dockerClient{api: docker, pullImages: pullImages}
}

type dockerClient struct {
	api        dockerclient.Client
	pullImages bool
}

func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
	cs := []Container{}

	log.Debug("Retrieving running containers")

	runningContainers, err := client.api.ListContainers(false, false, "")
	if err != nil {
		return nil, err
	}
	for _, runningContainer := range runningContainers {
		containerInfo, err := client.api.InspectContainer(runningContainer.Id)
		if err != nil {
			return nil, err
		}
		log.Debugf("Running container: %s - (%s)", containerInfo.Name, containerInfo.Id)

		imageInfo, err := client.api.InspectImage(containerInfo.Image)
		if err != nil {
			return nil, err
		}

		c := Container{containerInfo: containerInfo, imageInfo: imageInfo}
		if fn(c) {
			cs = append(cs, c)
		}
	}

	return cs, nil
}

func (client dockerClient) KillContainer(c Container, signal string, dryrun bool) error {
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sKilling %s (%s) with signal %s", prefix, c.Name(), c.ID(), signal)
	if !dryrun {
		if err := client.api.KillContainer(c.ID(), signal); err != nil {
			return err
		}
	}
	return nil
}

func (client dockerClient) StopContainer(c Container, timeout time.Duration, dryrun bool) error {
	signal := c.StopSignal()
	if signal == "" {
		signal = defaultStopSignal
	}
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sStopping %s (%s) with %s", prefix, c.Name(), c.ID(), signal)

	if !dryrun {
		if err := client.api.KillContainer(c.ID(), signal); err != nil {
			return err
		}

		// Wait for container to exit, but proceed anyway after the timeout elapses
		if err := client.waitForStop(c, timeout); err != nil {
			log.Debugf("Error waiting for container %s (%s) to stop: ''%s'", c.Name(), c.ID(), err.Error())
		}

		log.Debugf("Removing container %s", c.ID())

		if err := client.api.RemoveContainer(c.ID(), true, false); err != nil {
			return err
		}

		// Wait for container to be removed. In this case an error is a good thing
		if err := client.waitForStop(c, timeout); err == nil {
			return fmt.Errorf("Container %s (%s) could not be removed", c.Name(), c.ID())
		}
	}

	return nil
}

func (client dockerClient) StartContainer(c Container) error {
	config := c.runtimeConfig()
	hostConfig := c.hostConfig()
	name := c.Name()

	log.Infof("Starting %s", name)

	newContainerID, err := client.api.CreateContainer(config, name, nil)
	if err != nil {
		return err
	}

	log.Debugf("Starting container %s (%s)", name, newContainerID)

	return client.api.StartContainer(newContainerID, hostConfig)
}

func (client dockerClient) RenameContainer(c Container, newName string) error {
	log.Debugf("Renaming container %s (%s) to %s", c.Name(), c.ID(), newName)
	return client.api.RenameContainer(c.ID(), newName)
}

func (client dockerClient) RemoveImage(c Container, force bool, dryrun bool) error {
	imageID := c.ImageID()
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sRemoving image %s", prefix, imageID)
	if !dryrun {
		_, err := client.api.RemoveImage(imageID, force)
		return err
	}
	return nil
}

func (client dockerClient) RemoveContainer(c Container, force bool, dryrun bool) error {
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sRemoving container %s", prefix, c.ID())
	if !dryrun {
		return client.api.RemoveContainer(c.ID(), force, true)
	}
	return nil
}

func (client dockerClient) DisruptContainer(c Container, netemCmd string, dryrun bool) error {
	// find out if we have command, ip or command:ip
	cmd = strings.Split(netemCmd, ":")
	if len(cmd) == 2 {
		return disruptContainerFilterNetwork(c, cmd[0], cmd[1], dryrun)
	} else {// all network
		return disruptContainerAllNetwork(c, cmd[0], dryrun)
	}
}

func (client dockerClient) disruptContainerAllNetwork(c Container, netemCmd string, dryrun bool) error {
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sDisrupting container %s with netem cmd %s", prefix, c.ID(), netemCmd)
	if !dryrun {
		// use dockerclient ExecStart to run Traffic Control:
		// 'tc qdisc add dev eth0 root netem delay 100ms'
		// http://www.linuxfoundation.org/collaborate/workgroups/networking/netem
		netemCommand := "tc qdisc add dev eth0 root netem " + strings.ToLower(netemCmd)
		return execOnContainer(c, netemCommand)
	}
	return nil
}

func (client dockerClient) disruptContainerFilterNetwork(c Container, netemCmd string,
	targetIP string, dryrun bool) error {
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sDisrupting container %s with netem cmd %s on traffic to %s",
		prefix, c.ID(), netemCmd, targetIP)
	if !dryrun {
		// use dockerclient ExecStart to run Traffic Control
		// to filter network, needs to create a priority scheduling, add a low priority
		//  queue, apply netem command on that queue only, then route IP traffic
		//  to the low priority queue
		// 'tc qdisc add dev eth0 root handle 1: prio'
		// 'tc qdisc add dev eth0 parent 1:3 netem delay 3000ms'
		// 'tc filter add dev eth0 protocol ip parent 1:0 prio 3 /
		//  u32 match ip dst 172.19.0.3 flowid 1:3'
		// http://www.linuxfoundation.org/collaborate/workgroups/networking/netem
		handleCommand := "tc qdisc add dev eth0 root handle 1: prio"
		err_handle := execOnContainer(c, handleCommand)
		if err_handle != nil {
				return err_handle
			}

		netemCommand := "tc qdisc add dev eth0 parent 1:3 netem " + strings.ToLower(netemCmd)
		err_netem := execOnContainer(c, netemCommand)
		if err_netem != nil {
				return err_netem
			}

		filterCommand := "tc filter add dev eth0 protocol ip parent 1:0 prio 3 "+
			"u32 match ip dst " + strings.ToLower(targetIP) + " flowid 1:3"
		return execOnContainer(c, filterCommand)
	}
	return nil
}

func (client dockerClient) execOnContainer(c Container, execCmd string) error {
	execConfig := &dockerclient.ExecConfig{
		Cmd: strings.Split(execCmd, " "),
		Container: c.ID(),
	}
	_id, err := client.api.ExecCreate(execConfig)
	if err != nil {
			return err
		}

	log.Debugf("Starting Exec %s (%s)", execCmd, _id)
	return client.api.ExecStart(_id, execConfig)
}

func (client dockerClient) waitForStop(c Container, waitTime time.Duration) error {
	timeout := time.After(waitTime)

	for {
		select {
		case <-timeout:
			return nil
		default:
			if ci, err := client.api.InspectContainer(c.ID()); err != nil {
				return err
			} else if !ci.State.Running {
				return nil
			}
		}

		time.Sleep(1 * time.Second)
	}
}
<commit_msg>filter outgoing traffic so that only certain IP will be affected<commit_after>package container

import (
	"crypto/tls"
	"fmt"
	"time"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/samalba/dockerclient"
)

const (
	defaultStopSignal = "SIGTERM"
	dryRunPrefix      = "DRY: "
)

// A Filter is a prototype for a function that can be used to filter the
// results from a call to the ListContainers() method on the Client.
type Filter func(Container) bool

// A Client is the interface through which Pumba interacts with the Docker API.
type Client interface {
	ListContainers(Filter) ([]Container, error)
	StopContainer(Container, time.Duration, bool) error
	KillContainer(Container, string, bool) error
	StartContainer(Container) error
	RenameContainer(Container, string) error
	RemoveImage(Container, bool, bool) error
	RemoveContainer(Container, bool, bool) error
	DisruptContainer(Container, string, bool) error
}

// NewClient returns a new Client instance which can be used to interact with
// the Docker API.
func NewClient(dockerHost string, tlsConfig *tls.Config, pullImages bool) Client {
	docker, err := dockerclient.NewDockerClient(dockerHost, tlsConfig)

	if err != nil {
		log.Fatalf("Error instantiating Docker client: %s", err)
	}

	return dockerClient{api: docker, pullImages: pullImages}
}

type dockerClient struct {
	api        dockerclient.Client
	pullImages bool
}

func (client dockerClient) ListContainers(fn Filter) ([]Container, error) {
	cs := []Container{}

	log.Debug("Retrieving running containers")

	runningContainers, err := client.api.ListContainers(false, false, "")
	if err != nil {
		return nil, err
	}
	for _, runningContainer := range runningContainers {
		containerInfo, err := client.api.InspectContainer(runningContainer.Id)
		if err != nil {
			return nil, err
		}
		log.Debugf("Running container: %s - (%s)", containerInfo.Name, containerInfo.Id)

		imageInfo, err := client.api.InspectImage(containerInfo.Image)
		if err != nil {
			return nil, err
		}

		c := Container{containerInfo: containerInfo, imageInfo: imageInfo}
		if fn(c) {
			cs = append(cs, c)
		}
	}

	return cs, nil
}

func (client dockerClient) KillContainer(c Container, signal string, dryrun bool) error {
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sKilling %s (%s) with signal %s", prefix, c.Name(), c.ID(), signal)
	if !dryrun {
		if err := client.api.KillContainer(c.ID(), signal); err != nil {
			return err
		}
	}
	return nil
}

func (client dockerClient) StopContainer(c Container, timeout time.Duration, dryrun bool) error {
	signal := c.StopSignal()
	if signal == "" {
		signal = defaultStopSignal
	}
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sStopping %s (%s) with %s", prefix, c.Name(), c.ID(), signal)

	if !dryrun {
		if err := client.api.KillContainer(c.ID(), signal); err != nil {
			return err
		}

		// Wait for container to exit, but proceed anyway after the timeout elapses
		if err := client.waitForStop(c, timeout); err != nil {
			log.Debugf("Error waiting for container %s (%s) to stop: ''%s'", c.Name(), c.ID(), err.Error())
		}

		log.Debugf("Removing container %s", c.ID())

		if err := client.api.RemoveContainer(c.ID(), true, false); err != nil {
			return err
		}

		// Wait for container to be removed. In this case an error is a good thing
		if err := client.waitForStop(c, timeout); err == nil {
			return fmt.Errorf("Container %s (%s) could not be removed", c.Name(), c.ID())
		}
	}

	return nil
}

func (client dockerClient) StartContainer(c Container) error {
	config := c.runtimeConfig()
	hostConfig := c.hostConfig()
	name := c.Name()

	log.Infof("Starting %s", name)

	newContainerID, err := client.api.CreateContainer(config, name, nil)
	if err != nil {
		return err
	}

	log.Debugf("Starting container %s (%s)", name, newContainerID)

	return client.api.StartContainer(newContainerID, hostConfig)
}

func (client dockerClient) RenameContainer(c Container, newName string) error {
	log.Debugf("Renaming container %s (%s) to %s", c.Name(), c.ID(), newName)
	return client.api.RenameContainer(c.ID(), newName)
}

func (client dockerClient) RemoveImage(c Container, force bool, dryrun bool) error {
	imageID := c.ImageID()
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sRemoving image %s", prefix, imageID)
	if !dryrun {
		_, err := client.api.RemoveImage(imageID, force)
		return err
	}
	return nil
}

func (client dockerClient) RemoveContainer(c Container, force bool, dryrun bool) error {
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sRemoving container %s", prefix, c.ID())
	if !dryrun {
		return client.api.RemoveContainer(c.ID(), force, true)
	}
	return nil
}

func (client dockerClient) DisruptContainer(c Container, netemCmd string, dryrun bool) error {
	// find out if we have command, ip or command:ip
	cmd = strings.Split(netemCmd, ":")
	if len(cmd) == 2 {
		return disruptContainerFilterNetwork(c, cmd[0], cmd[1], dryrun)
	} else {
		return disruptContainerAllNetwork(c, cmd[0], dryrun)
	}
}

func (client dockerClient) disruptContainerAllNetwork(c Container, netemCmd string, dryrun bool) error {
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sDisrupting container %s with netem cmd %s", prefix, c.ID(), netemCmd)
	if !dryrun {
		// use dockerclient ExecStart to run Traffic Control:
		// 'tc qdisc add dev eth0 root netem delay 100ms'
		// http://www.linuxfoundation.org/collaborate/workgroups/networking/netem
		netemCommand := "tc qdisc add dev eth0 root netem " + strings.ToLower(netemCmd)
		return execOnContainer(c, netemCommand)
	}
	return nil
}

func (client dockerClient) disruptContainerFilterNetwork(c Container, netemCmd string,
	targetIP string, dryrun bool) error {
	prefix := ""
	if dryrun {
		prefix = dryRunPrefix
	}
	log.Infof("%sDisrupting container %s with netem cmd %s on traffic to %s",
		prefix, c.ID(), netemCmd, targetIP)
	if !dryrun {
		// use dockerclient ExecStart to run Traffic Control
		// to filter network, needs to create a priority scheduling, add a low priority
		//  queue, apply netem command on that queue only, then route IP traffic
		//  to the low priority queue
		// 'tc qdisc add dev eth0 root handle 1: prio'
		// 'tc qdisc add dev eth0 parent 1:3 netem delay 3000ms'
		// 'tc filter add dev eth0 protocol ip parent 1:0 prio 3 /
		//  u32 match ip dst 172.19.0.3 flowid 1:3'
		// http://www.linuxfoundation.org/collaborate/workgroups/networking/netem
		handleCommand := "tc qdisc add dev eth0 root handle 1: prio"
		err_handle := execOnContainer(c, handleCommand)
		if err_handle != nil {
				return err_handle
			}

		netemCommand := "tc qdisc add dev eth0 parent 1:3 netem " + strings.ToLower(netemCmd)
		err_netem := execOnContainer(c, netemCommand)
		if err_netem != nil {
				return err_netem
			}

		filterCommand := "tc filter add dev eth0 protocol ip parent 1:0 prio 3 "+
			"u32 match ip dst " + strings.ToLower(targetIP) + " flowid 1:3"
		return execOnContainer(c, filterCommand)
	}
	return nil
}

func (client dockerClient) execOnContainer(c Container, execCmd string) error {
	execConfig := &dockerclient.ExecConfig{
		Cmd: strings.Split(execCmd, " "),
		Container: c.ID(),
	}
	_id, err := client.api.ExecCreate(execConfig)
	if err != nil {
			return err
		}

	log.Debugf("Starting Exec %s (%s)", execCmd, _id)
	return client.api.ExecStart(_id, execConfig)
}

func (client dockerClient) waitForStop(c Container, waitTime time.Duration) error {
	timeout := time.After(waitTime)

	for {
		select {
		case <-timeout:
			return nil
		default:
			if ci, err := client.api.InspectContainer(c.ID()); err != nil {
				return err
			} else if !ci.State.Running {
				return nil
			}
		}

		time.Sleep(1 * time.Second)
	}
}
<|endoftext|>


<commit_before>// A web app for Google App Engine that proxies HTTP requests and responses to a
// Tor relay running meek-server.
package reflect

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

	"appengine"
	"appengine/urlfetch"
)

const (
	forwardURL = "http://meek.bamsoftware.com:7002/"
	// A timeout of 0 means to use the App Engine default (5 seconds).
	urlFetchTimeout = 20 * time.Second
)

var context appengine.Context

// Join two URL paths.
func pathJoin(a, b string) string {
	if len(a) > 0 && a[len(a)-1] == '/' {
		a = a[:len(a)-1]
	}
	if len(b) == 0 || b[0] != '/' {
		b = "/" + b
	}
	return a + b
}

// We reflect only a whitelisted set of header fields. In requests, the full
// list includes things like User-Agent and X-Appengine-Country that the Tor
// bridge doesn't need to know. In responses, there may be things like
// Transfer-Encoding that interfere with App Engine's own hop-by-hop headers.
var reflectedHeaderFields = []string{
	"Content-Type",
	"X-Session-Id",
}

// Make a copy of r, with the URL being changed to be relative to forwardURL,
// and including only the headers in reflectedHeaderFields.
func copyRequest(r *http.Request) (*http.Request, error) {
	u, err := url.Parse(forwardURL)
	if err != nil {
		return nil, err
	}
	// Append the requested path to the path in forwardURL, so that
	// forwardURL can be something like "http://example.com/reflect".
	u.Path = pathJoin(u.Path, r.URL.Path)
	c, err := http.NewRequest(r.Method, u.String(), r.Body)
	if err != nil {
		return nil, err
	}
	for _, key := range reflectedHeaderFields {
		value := r.Header.Get(key)
		if value != "" {
			c.Header.Add(key, value)
		}
	}
	return c, nil
}

func handler(w http.ResponseWriter, r *http.Request) {
	context = appengine.NewContext(r)
	fr, err := copyRequest(r)
	if err != nil {
		context.Errorf("copyRequest: %s", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	// Use urlfetch.Transport directly instead of urlfetch.Client because we
	// want only a single HTTP transaction, not following redirects.
	transport := urlfetch.Transport{
		Context: context,
		// Despite the name, Transport.Deadline is really a timeout and
		// not an absolute deadline as used in the net package. In
		// other words it is a time.Duration, not a time.Time.
		Deadline: urlFetchTimeout,
	}
	resp, err := transport.RoundTrip(fr)
	if err != nil {
		context.Errorf("RoundTrip: %s", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()
	for _, key := range reflectedHeaderFields {
		value := resp.Header.Get(key)
		if value != "" {
			w.Header().Add(key, value)
		}
	}
	w.WriteHeader(resp.StatusCode)
	n, err := io.Copy(w, resp.Body)
	if err != nil {
		context.Errorf("io.Copy after %d bytes: %s", n, err)
	}
}

func init() {
	http.HandleFunc("/", handler)
}
<commit_msg>We don't need to reflect Content-Type.<commit_after>// A web app for Google App Engine that proxies HTTP requests and responses to a
// Tor relay running meek-server.
package reflect

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

	"appengine"
	"appengine/urlfetch"
)

const (
	forwardURL = "http://meek.bamsoftware.com:7002/"
	// A timeout of 0 means to use the App Engine default (5 seconds).
	urlFetchTimeout = 20 * time.Second
)

var context appengine.Context

// Join two URL paths.
func pathJoin(a, b string) string {
	if len(a) > 0 && a[len(a)-1] == '/' {
		a = a[:len(a)-1]
	}
	if len(b) == 0 || b[0] != '/' {
		b = "/" + b
	}
	return a + b
}

// We reflect only a whitelisted set of header fields. In requests, the full
// list includes things like User-Agent and X-Appengine-Country that the Tor
// bridge doesn't need to know. In responses, there may be things like
// Transfer-Encoding that interfere with App Engine's own hop-by-hop headers.
var reflectedHeaderFields = []string{
	"X-Session-Id",
}

// Make a copy of r, with the URL being changed to be relative to forwardURL,
// and including only the headers in reflectedHeaderFields.
func copyRequest(r *http.Request) (*http.Request, error) {
	u, err := url.Parse(forwardURL)
	if err != nil {
		return nil, err
	}
	// Append the requested path to the path in forwardURL, so that
	// forwardURL can be something like "http://example.com/reflect".
	u.Path = pathJoin(u.Path, r.URL.Path)
	c, err := http.NewRequest(r.Method, u.String(), r.Body)
	if err != nil {
		return nil, err
	}
	for _, key := range reflectedHeaderFields {
		value := r.Header.Get(key)
		if value != "" {
			c.Header.Add(key, value)
		}
	}
	return c, nil
}

func handler(w http.ResponseWriter, r *http.Request) {
	context = appengine.NewContext(r)
	fr, err := copyRequest(r)
	if err != nil {
		context.Errorf("copyRequest: %s", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	// Use urlfetch.Transport directly instead of urlfetch.Client because we
	// want only a single HTTP transaction, not following redirects.
	transport := urlfetch.Transport{
		Context: context,
		// Despite the name, Transport.Deadline is really a timeout and
		// not an absolute deadline as used in the net package. In
		// other words it is a time.Duration, not a time.Time.
		Deadline: urlFetchTimeout,
	}
	resp, err := transport.RoundTrip(fr)
	if err != nil {
		context.Errorf("RoundTrip: %s", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()
	for _, key := range reflectedHeaderFields {
		value := resp.Header.Get(key)
		if value != "" {
			w.Header().Add(key, value)
		}
	}
	w.WriteHeader(resp.StatusCode)
	n, err := io.Copy(w, resp.Body)
	if err != nil {
		context.Errorf("io.Copy after %d bytes: %s", n, err)
	}
}

func init() {
	http.HandleFunc("/", handler)
}
<|endoftext|>