<commit_before>/*
Copyright 2017 The Kubernetes 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 handlers

import (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/audit"
	"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
	"k8s.io/apiserver/pkg/endpoints/handlers/finisher"
	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
	"k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/features"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/apiserver/pkg/util/dryrun"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	utiltrace "k8s.io/utils/trace"
)

var namespaceGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}

func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		// For performance tracking purposes.
		trace := utiltrace.New("Create", traceFields(req)...)
		defer trace.LogIfLong(500 * time.Millisecond)

		if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
			scope.err(errors.NewBadRequest("the dryRun feature is disabled"), w, req)
			return
		}

		namespace, name, err := scope.Namer.Name(req)
		if err != nil {
			if includeName {
				// name was required, return
				scope.err(err, w, req)
				return
			}

			// otherwise attempt to look up the namespace
			namespace, err = scope.Namer.Namespace(req)
			if err != nil {
				scope.err(err, w, req)
				return
			}
		}

		// enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided
		// timeout inside the parent context is lower than requestTimeoutUpperBound.
		ctx, cancel := context.WithTimeout(req.Context(), requestTimeoutUpperBound)
		defer cancel()
		outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		gv := scope.Kind.GroupVersion()
		s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		decoder := scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion)

		body, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		options := &metav1.CreateOptions{}
		values := req.URL.Query()
		if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil {
			err = errors.NewBadRequest(err.Error())
			scope.err(err, w, req)
			return
		}
		if errs := validation.ValidateCreateOptions(options); len(errs) > 0 {
			err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "CreateOptions"}, "", errs)
			scope.err(err, w, req)
			return
		}
		options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))

		defaultGVK := scope.Kind
		original := r.New()
		trace.Step("About to convert to expected version")
		obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
		if err != nil {
			err = transformDecodeError(scope.Typer, err, original, gvk, body)
			scope.err(err, w, req)
			return
		}
		if !scope.AcceptsGroupVersion(gvk.GroupVersion()) {
			err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%v)", gvk.GroupVersion().String(), gv.String()))
			scope.err(err, w, req)
			return
		}
		trace.Step("Conversion done")

		// On create, get name from new object if unset
		if len(name) == 0 {
			_, name, _ = scope.Namer.ObjectName(obj)
		}
		if len(namespace) == 0 && *gvk == namespaceGVK {
			namespace = name
		}
		ctx = request.WithNamespace(ctx, namespace)

		ae := request.AuditEventFrom(ctx)
		admit = admission.WithAudit(admit, ae)
		objGV := schema.GroupVersion{Group: gvk.Group, Version: gvk.Version}
		audit.LogRequestObject(ae, obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)

		userInfo, _ := request.UserFrom(ctx)

		trace.Step("About to store object in database")
		admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
		requestFunc := func() (runtime.Object, error) {
			return r.Create(
				ctx,
				name,
				obj,
				rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
				options,
			)
		}
		// Dedup owner references before updating managed fields
		dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
		result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
			if scope.FieldManager != nil {
				liveObj, err := scope.Creater.New(scope.Kind)
				if err != nil {
					return nil, fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err)
				}
				obj = scope.FieldManager.UpdateNoErrors(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
				admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit)
			}
			if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
				if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
					return nil, err
				}
			}
			// Dedup owner references again after mutating admission happens
			dedupOwnerReferencesAndAddWarning(obj, req.Context(), true)
			result, err := requestFunc()
			// If the object wasn't committed to storage because it's serialized size was too large,
			// it is safe to remove managedFields (which can be large) and try again.
			if isTooLargeError(err) {
				if accessor, accessorErr := meta.Accessor(obj); accessorErr == nil {
					accessor.SetManagedFields(nil)
					result, err = requestFunc()
				}
			}
			return result, err
		})
		if err != nil {
			scope.err(err, w, req)
			return
		}
		trace.Step("Object stored in database")

		code := http.StatusCreated
		status, ok := result.(*metav1.Status)
		if ok && err == nil && status.Code == 0 {
			status.Code = int32(code)
		}

		transformResponseObject(ctx, scope, trace, req, w, code, outputMediaType, result)
	}
}

// CreateNamedResource returns a function that will handle a resource creation with name.
func CreateNamedResource(r rest.NamedCreater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
	return createHandler(r, scope, admission, true)
}

// CreateResource returns a function that will handle a resource creation.
func CreateResource(r rest.Creater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
	return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
}

type namedCreaterAdapter struct {
	rest.Creater
}

func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	return c.Creater.Create(ctx, obj, createValidatingAdmission, options)
}

// manager is assumed to be already a valid value, we need to make
// userAgent into a valid value too.
func managerOrUserAgent(manager, userAgent string) string {
	if manager != "" {
		return manager
	}
	return prefixFromUserAgent(userAgent)
}

// prefixFromUserAgent takes the characters preceding the first /, quote
// unprintable character and then trim what's beyond the
// FieldManagerMaxLength limit.
func prefixFromUserAgent(u string) string {
	m := strings.Split(u, "/")[0]
	buf := bytes.NewBuffer(nil)
	for _, r := range m {
		// Ignore non-printable characters
		if !unicode.IsPrint(r) {
			continue
		}
		// Only append if we have room for it
		if buf.Len()+utf8.RuneLen(r) > validation.FieldManagerMaxLength {
			break
		}
		buf.WriteRune(r)
	}
	return buf.String()
}
<commit_msg>Simplify objGV construction<commit_after>/*
Copyright 2017 The Kubernetes 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 handlers

import (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/audit"
	"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
	"k8s.io/apiserver/pkg/endpoints/handlers/finisher"
	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
	"k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/features"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/apiserver/pkg/util/dryrun"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	utiltrace "k8s.io/utils/trace"
)

var namespaceGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}

func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		// For performance tracking purposes.
		trace := utiltrace.New("Create", traceFields(req)...)
		defer trace.LogIfLong(500 * time.Millisecond)

		if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
			scope.err(errors.NewBadRequest("the dryRun feature is disabled"), w, req)
			return
		}

		namespace, name, err := scope.Namer.Name(req)
		if err != nil {
			if includeName {
				// name was required, return
				scope.err(err, w, req)
				return
			}

			// otherwise attempt to look up the namespace
			namespace, err = scope.Namer.Namespace(req)
			if err != nil {
				scope.err(err, w, req)
				return
			}
		}

		// enforce a timeout of at most requestTimeoutUpperBound (34s) or less if the user-provided
		// timeout inside the parent context is lower than requestTimeoutUpperBound.
		ctx, cancel := context.WithTimeout(req.Context(), requestTimeoutUpperBound)
		defer cancel()
		outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		gv := scope.Kind.GroupVersion()
		s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		decoder := scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion)

		body, err := limitedReadBody(req, scope.MaxRequestBodyBytes)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		options := &metav1.CreateOptions{}
		values := req.URL.Query()
		if err := metainternalversionscheme.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil {
			err = errors.NewBadRequest(err.Error())
			scope.err(err, w, req)
			return
		}
		if errs := validation.ValidateCreateOptions(options); len(errs) > 0 {
			err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "CreateOptions"}, "", errs)
			scope.err(err, w, req)
			return
		}
		options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))

		defaultGVK := scope.Kind
		original := r.New()
		trace.Step("About to convert to expected version")
		obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
		if err != nil {
			err = transformDecodeError(scope.Typer, err, original, gvk, body)
			scope.err(err, w, req)
			return
		}
		if !scope.AcceptsGroupVersion(gvk.GroupVersion()) {
			err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%v)", gvk.GroupVersion().String(), gv.String()))
			scope.err(err, w, req)
			return
		}
		trace.Step("Conversion done")

		// On create, get name from new object if unset
		if len(name) == 0 {
			_, name, _ = scope.Namer.ObjectName(obj)
		}
		if len(namespace) == 0 && *gvk == namespaceGVK {
			namespace = name
		}
		ctx = request.WithNamespace(ctx, namespace)

		ae := request.AuditEventFrom(ctx)
		admit = admission.WithAudit(admit, ae)
		objGV := gvk.GroupVersion()
		audit.LogRequestObject(ae, obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)

		userInfo, _ := request.UserFrom(ctx)

		trace.Step("About to store object in database")
		admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
		requestFunc := func() (runtime.Object, error) {
			return r.Create(
				ctx,
				name,
				obj,
				rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
				options,
			)
		}
		// Dedup owner references before updating managed fields
		dedupOwnerReferencesAndAddWarning(obj, req.Context(), false)
		result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
			if scope.FieldManager != nil {
				liveObj, err := scope.Creater.New(scope.Kind)
				if err != nil {
					return nil, fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err)
				}
				obj = scope.FieldManager.UpdateNoErrors(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
				admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit)
			}
			if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
				if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
					return nil, err
				}
			}
			// Dedup owner references again after mutating admission happens
			dedupOwnerReferencesAndAddWarning(obj, req.Context(), true)
			result, err := requestFunc()
			// If the object wasn't committed to storage because it's serialized size was too large,
			// it is safe to remove managedFields (which can be large) and try again.
			if isTooLargeError(err) {
				if accessor, accessorErr := meta.Accessor(obj); accessorErr == nil {
					accessor.SetManagedFields(nil)
					result, err = requestFunc()
				}
			}
			return result, err
		})
		if err != nil {
			scope.err(err, w, req)
			return
		}
		trace.Step("Object stored in database")

		code := http.StatusCreated
		status, ok := result.(*metav1.Status)
		if ok && err == nil && status.Code == 0 {
			status.Code = int32(code)
		}

		transformResponseObject(ctx, scope, trace, req, w, code, outputMediaType, result)
	}
}

// CreateNamedResource returns a function that will handle a resource creation with name.
func CreateNamedResource(r rest.NamedCreater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
	return createHandler(r, scope, admission, true)
}

// CreateResource returns a function that will handle a resource creation.
func CreateResource(r rest.Creater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
	return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
}

type namedCreaterAdapter struct {
	rest.Creater
}

func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	return c.Creater.Create(ctx, obj, createValidatingAdmission, options)
}

// manager is assumed to be already a valid value, we need to make
// userAgent into a valid value too.
func managerOrUserAgent(manager, userAgent string) string {
	if manager != "" {
		return manager
	}
	return prefixFromUserAgent(userAgent)
}

// prefixFromUserAgent takes the characters preceding the first /, quote
// unprintable character and then trim what's beyond the
// FieldManagerMaxLength limit.
func prefixFromUserAgent(u string) string {
	m := strings.Split(u, "/")[0]
	buf := bytes.NewBuffer(nil)
	for _, r := range m {
		// Ignore non-printable characters
		if !unicode.IsPrint(r) {
			continue
		}
		// Only append if we have room for it
		if buf.Len()+utf8.RuneLen(r) > validation.FieldManagerMaxLength {
			break
		}
		buf.WriteRune(r)
	}
	return buf.String()
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      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 gcpbuildpack is a framework for implementing buildpacks (https://buildpacks.io/).
package gcpbuildpack

import (
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/buildpacks/pkg/env"
	libbuild "github.com/buildpack/libbuildpack/build"
	"github.com/buildpack/libbuildpack/buildpack"
	"github.com/buildpack/libbuildpack/buildpackplan"
	"github.com/buildpack/libbuildpack/buildplan"
	libdetect "github.com/buildpack/libbuildpack/detect"
	"github.com/buildpack/libbuildpack/layers"
)

const (
	// cacheHitMessage is emitted by ctx.CacheHit(). Must match acceptance test value.
	cacheHitMessage = "***** CACHE HIT:"

	// cacheMissMessage is emitted by ctx.CacheMiss(). Must match acceptance test value.
	cacheMissMessage = "***** CACHE MISS:"
)

var (
	logger = log.New(os.Stderr, "", 0)
)

// DetectFn is the callback signature for Detect()
type DetectFn func(*Context) error

// BuildFn is the callback signature for Build()
type BuildFn func(*Context) error

type stats struct {
	spans []*spanInfo
	user  time.Duration
}

// Context provides contextually aware functions for buildpack authors.
type Context struct {
	info            buildpack.Info
	applicationRoot string
	buildpackRoot   string
	exitCode        int
	buildPlan       buildplan.Plan
	buildpackPlans  []buildpackplan.Plan
	debug           bool
	processes       layers.Processes
	d               *libdetect.Detect
	b               *libbuild.Build
	stats           stats
}

// NewContext creates a context.
func NewContext(info buildpack.Info) *Context {
	debug, err := env.IsDebugMode()
	if err != nil {
		logger.Printf("Failed to parse debug mode: %v", err)
		os.Exit(1)
	}
	return &Context{
		debug: debug,
		info:  info,
	}
}

// NewContextForTests creates a context to be used for tests.
func NewContextForTests(info buildpack.Info, root string) *Context {
	ctx := NewContext(info)
	ctx.applicationRoot = root
	return ctx
}

func newDetectContext() *Context {
	d, err := libdetect.DefaultDetect()
	if err != nil {
		logger.Printf("Failed to initialize /bin/detect: %v", err)
		os.Exit(1)
	}
	ctx := NewContext(d.Buildpack.Info)
	ctx.d = &d
	ctx.applicationRoot = ctx.d.Application.Root
	ctx.buildpackRoot = ctx.d.Buildpack.Root
	return ctx
}

func newBuildContext() *Context {
	b, err := libbuild.DefaultBuild()
	if err != nil {
		logger.Printf("Failed to initialize /bin/build: %v", err)
		os.Exit(1)
	}
	ctx := NewContext(b.Buildpack.Info)
	ctx.b = &b
	ctx.applicationRoot = ctx.b.Application.Root
	ctx.buildpackRoot = ctx.b.Buildpack.Root
	return ctx
}

// BuildpackID returns the buildpack id.
func (ctx *Context) BuildpackID() string {
	return ctx.info.ID
}

// BuildpackVersion returns the buildpack version.
func (ctx *Context) BuildpackVersion() string {
	return ctx.info.Version
}

// BuildpackName returns the buildpack name.
func (ctx *Context) BuildpackName() string {
	return ctx.info.Name
}

// ApplicationRoot returns the root folder of the application code.
func (ctx *Context) ApplicationRoot() string {
	return ctx.applicationRoot
}

// BuildpackRoot returns the root folder of the buildpack.
func (ctx *Context) BuildpackRoot() string {
	return ctx.buildpackRoot
}

// Debug returns whether debug mode is enabled.
func (ctx *Context) Debug() bool {
	return ctx.debug
}

// Main is the main entrypoint to a buildpack's detect and build functions.
func Main(d DetectFn, b BuildFn) {
	switch filepath.Base(os.Args[0]) {
	case "detect":
		detect(d)
	case "build":
		build(b)
	default:
		logger.Print("Unknown command, expected 'detect' or 'build'.")
		os.Exit(1)
	}
}

// detect implements the /bin/detect phase of the buildpack.
func detect(f DetectFn) {
	ctx := newDetectContext()
	status := StatusInternal
	defer func(now time.Time) {
		ctx.Span(fmt.Sprintf("Buildpack Detect %s", ctx.info.ID), now, status)
	}(time.Now())

	if err := f(ctx); err != nil {
		msg := fmt.Sprintf("Failed to run /bin/detect: %v", err)
		var be *Error
		if errors.As(err, &be) {
			status = be.Status
			ctx.Exit(ctx.d.Error(1), be)
		}
		ctx.Exit(ctx.d.Error(1), Errorf(status, msg))
	}

	_, err := ctx.d.Pass(ctx.buildPlan)
	if err != nil {
		ctx.Exit(ctx.d.Error(1), Errorf(StatusInternal, err.Error()))
	}

	status = StatusOk
}

func build(b BuildFn) {
	start := time.Now()
	ctx := newBuildContext()
	ctx.Logf("=== %s (%s@%s) ===", ctx.BuildpackName(), ctx.BuildpackID(), ctx.BuildpackVersion())

	status := StatusInternal
	defer func(now time.Time) {
		ctx.Span(fmt.Sprintf("Buildpack Build %s", ctx.BuildpackID()), now, status)
	}(time.Now())

	if err := b(ctx); err != nil {
		msg := fmt.Sprintf("Failed to run /bin/build: %v", err)
		var be *Error
		if errors.As(err, &be) {
			status = be.Status
			ctx.Exit(ctx.b.Failure(1), be)
		}
		ctx.Exit(ctx.b.Failure(1), Errorf(status, msg))
	}

	// Emit application metadata.
	if len(ctx.processes) > 0 {
		metadata := layers.Metadata{Processes: ctx.processes}
		if err := ctx.b.Layers.WriteApplicationMetadata(metadata); err != nil {
			ctx.Exit(ctx.b.Failure(1), Errorf(StatusInternal, "writing application metadata: %v", err))
		}
	}

	if _, err := ctx.b.Success(ctx.buildpackPlans...); err != nil {
		ctx.Exit(ctx.b.Failure(1), Errorf(StatusInternal, err.Error()))
	}

	status = StatusOk
	ctx.saveSuccessOutput(time.Since(start))
}

// Exit causes the buildpack to exit with the given exit code and message.
func (ctx *Context) Exit(exitCode int, be *Error) {
	if be != nil {
		ctx.Logf("Failure: " + be.Message)
		ctx.saveErrorOutput(be)
	}

	if exitCode != 0 {
		ctx.Tipf(divider)
		ctx.Tipf(`Sorry your project couldn't be built.`)
		ctx.Tipf(`Our documentation explains ways to configure Buildpacks to better recognise your project:`)
		ctx.Tipf(` -> https://github.com/GoogleCloudPlatform/buildpacks/blob/master/README.md`)
		ctx.Tipf(`If you think you've found an issue, please report it:`)
		ctx.Tipf(` -> https://github.com/GoogleCloudPlatform/buildpacks/issues/new`)
		ctx.Tipf(divider)
	}

	ctx.exitCode = exitCode
	os.Exit(exitCode)
}

// OptOut is used during the detect phase to opt out of the build process.
func (ctx *Context) OptOut(format string, args ...interface{}) {
	ctx.Logf(format, args...)
	os.Exit(libdetect.FailStatusCode)
}

// OptIn is used during the detect phase to opt in to the build process.
func (ctx *Context) OptIn(format string, args ...interface{}) {
	ctx.Logf(format, args...)
	os.Exit(libdetect.PassStatusCode)
}

// Logf emits a structured logging line.
func (ctx *Context) Logf(format string, args ...interface{}) {
	logger.Printf(format, args...)
}

// Debugf emits a structured logging line if the debug flag is set.
func (ctx *Context) Debugf(format string, args ...interface{}) {
	if !ctx.debug {
		return
	}
	ctx.Logf("DEBUG: "+format, args...)
}

// Warnf emits a structured logging line for warnings.
func (ctx *Context) Warnf(format string, args ...interface{}) {
	ctx.Logf("Warning: "+format, args...)
}

// Tipf emits a structured logging line for usage tips.
func (ctx *Context) Tipf(format string, args ...interface{}) {
	// Tips are only displayed for the gcp/base builder, not in GAE/GCF environments.
	if os.Getenv("CNB_STACK_ID") == "google" {
		ctx.Logf(format, args...)
	}
}

// CacheHit records a cache hit debug message. This is used in acceptance test validation.
func (ctx *Context) CacheHit(tag string) {
	ctx.Debugf("%s %q", cacheHitMessage, tag)
}

// CacheMiss records a cache miss debug message. This is used in acceptance test validation.
func (ctx *Context) CacheMiss(tag string) {
	ctx.Debugf("%s %q", cacheMissMessage, tag)
}

// Span emits a structured Stackdriver span.
func (ctx *Context) Span(label string, start time.Time, status Status) {
	now := time.Now()
	attributes := map[string]interface{}{
		"/buildpack_id":      ctx.BuildpackID(),
		"/buildpack_name":    ctx.BuildpackName(),
		"/buildpack_version": ctx.BuildpackVersion(),
	}
	si, err := newSpanInfo(label, start, now, attributes, status)
	if err != nil {
		ctx.Logf("Warning: invalid span dropped: %v", err)
	}
	ctx.stats.spans = append(ctx.stats.spans, si)
}

// AddBuildPlanProvides adds a provided dependency to the build plan.
func (ctx *Context) AddBuildPlanProvides(provided buildplan.Provided) {
	ctx.buildPlan.Provides = append(ctx.buildPlan.Provides, provided)
}

// AddBuildPlanRequires adds a required dependency to the build plan.
func (ctx *Context) AddBuildPlanRequires(required buildplan.Required) {
	ctx.buildPlan.Requires = append(ctx.buildPlan.Requires, required)
}

// AddBuildpackPlan adds a required dependency to the build plan.
func (ctx *Context) AddBuildpackPlan(plan buildpackplan.Plan) {
	ctx.buildpackPlans = append(ctx.buildpackPlans, plan)
}

// AddWebProcess adds the given command as the web start process, overwriting any previous web start process.
func (ctx *Context) AddWebProcess(cmd []string) {
	current := ctx.processes
	ctx.processes = layers.Processes{}
	for _, p := range current {
		if p.Type == "web" {
			ctx.Logf("Warning: overwriting existing web process %q.", p.Command)
			continue // Do not add this item back to the ctx.processes; we are overwriting it.
		}
		ctx.processes = append(ctx.processes, p)
	}
	p := layers.Process{
		Type:    "web",
		Command: cmd[0],
		Direct:  true, // Uses Exec (no shell).
	}
	if len(cmd) > 1 {
		p.Args = cmd[1:]
	}
	ctx.processes = append(ctx.processes, p)
}

// HTTPStatus returns the status code for a url.
func (ctx *Context) HTTPStatus(url string) int {
	result := ctx.Exec([]string{"curl", "--head", "-w", "%{http_code}", "-o", "/dev/null", "--silent", "--location", "--retry", "3", url})
	cs := strings.TrimSpace(result.Stdout)
	c, err := strconv.Atoi(cs)
	if err != nil {
		ctx.Exit(1, UserErrorf("Unexpected response code %q from %s.", cs, url))
	}
	return c
}
<commit_msg>Emit Error ID on error exit.<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      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 gcpbuildpack is a framework for implementing buildpacks (https://buildpacks.io/).
package gcpbuildpack

import (
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/buildpacks/pkg/env"
	libbuild "github.com/buildpack/libbuildpack/build"
	"github.com/buildpack/libbuildpack/buildpack"
	"github.com/buildpack/libbuildpack/buildpackplan"
	"github.com/buildpack/libbuildpack/buildplan"
	libdetect "github.com/buildpack/libbuildpack/detect"
	"github.com/buildpack/libbuildpack/layers"
)

const (
	// cacheHitMessage is emitted by ctx.CacheHit(). Must match acceptance test value.
	cacheHitMessage = "***** CACHE HIT:"

	// cacheMissMessage is emitted by ctx.CacheMiss(). Must match acceptance test value.
	cacheMissMessage = "***** CACHE MISS:"
)

var (
	logger = log.New(os.Stderr, "", 0)
)

// DetectFn is the callback signature for Detect()
type DetectFn func(*Context) error

// BuildFn is the callback signature for Build()
type BuildFn func(*Context) error

type stats struct {
	spans []*spanInfo
	user  time.Duration
}

// Context provides contextually aware functions for buildpack authors.
type Context struct {
	info            buildpack.Info
	applicationRoot string
	buildpackRoot   string
	exitCode        int
	buildPlan       buildplan.Plan
	buildpackPlans  []buildpackplan.Plan
	debug           bool
	processes       layers.Processes
	d               *libdetect.Detect
	b               *libbuild.Build
	stats           stats
}

// NewContext creates a context.
func NewContext(info buildpack.Info) *Context {
	debug, err := env.IsDebugMode()
	if err != nil {
		logger.Printf("Failed to parse debug mode: %v", err)
		os.Exit(1)
	}
	return &Context{
		debug: debug,
		info:  info,
	}
}

// NewContextForTests creates a context to be used for tests.
func NewContextForTests(info buildpack.Info, root string) *Context {
	ctx := NewContext(info)
	ctx.applicationRoot = root
	return ctx
}

func newDetectContext() *Context {
	d, err := libdetect.DefaultDetect()
	if err != nil {
		logger.Printf("Failed to initialize /bin/detect: %v", err)
		os.Exit(1)
	}
	ctx := NewContext(d.Buildpack.Info)
	ctx.d = &d
	ctx.applicationRoot = ctx.d.Application.Root
	ctx.buildpackRoot = ctx.d.Buildpack.Root
	return ctx
}

func newBuildContext() *Context {
	b, err := libbuild.DefaultBuild()
	if err != nil {
		logger.Printf("Failed to initialize /bin/build: %v", err)
		os.Exit(1)
	}
	ctx := NewContext(b.Buildpack.Info)
	ctx.b = &b
	ctx.applicationRoot = ctx.b.Application.Root
	ctx.buildpackRoot = ctx.b.Buildpack.Root
	return ctx
}

// BuildpackID returns the buildpack id.
func (ctx *Context) BuildpackID() string {
	return ctx.info.ID
}

// BuildpackVersion returns the buildpack version.
func (ctx *Context) BuildpackVersion() string {
	return ctx.info.Version
}

// BuildpackName returns the buildpack name.
func (ctx *Context) BuildpackName() string {
	return ctx.info.Name
}

// ApplicationRoot returns the root folder of the application code.
func (ctx *Context) ApplicationRoot() string {
	return ctx.applicationRoot
}

// BuildpackRoot returns the root folder of the buildpack.
func (ctx *Context) BuildpackRoot() string {
	return ctx.buildpackRoot
}

// Debug returns whether debug mode is enabled.
func (ctx *Context) Debug() bool {
	return ctx.debug
}

// Main is the main entrypoint to a buildpack's detect and build functions.
func Main(d DetectFn, b BuildFn) {
	switch filepath.Base(os.Args[0]) {
	case "detect":
		detect(d)
	case "build":
		build(b)
	default:
		logger.Print("Unknown command, expected 'detect' or 'build'.")
		os.Exit(1)
	}
}

// detect implements the /bin/detect phase of the buildpack.
func detect(f DetectFn) {
	ctx := newDetectContext()
	status := StatusInternal
	defer func(now time.Time) {
		ctx.Span(fmt.Sprintf("Buildpack Detect %s", ctx.info.ID), now, status)
	}(time.Now())

	if err := f(ctx); err != nil {
		msg := fmt.Sprintf("Failed to run /bin/detect: %v", err)
		var be *Error
		if errors.As(err, &be) {
			status = be.Status
			ctx.Exit(ctx.d.Error(1), be)
		}
		ctx.Exit(ctx.d.Error(1), Errorf(status, msg))
	}

	_, err := ctx.d.Pass(ctx.buildPlan)
	if err != nil {
		ctx.Exit(ctx.d.Error(1), Errorf(StatusInternal, err.Error()))
	}

	status = StatusOk
}

func build(b BuildFn) {
	start := time.Now()
	ctx := newBuildContext()
	ctx.Logf("=== %s (%s@%s) ===", ctx.BuildpackName(), ctx.BuildpackID(), ctx.BuildpackVersion())

	status := StatusInternal
	defer func(now time.Time) {
		ctx.Span(fmt.Sprintf("Buildpack Build %s", ctx.BuildpackID()), now, status)
	}(time.Now())

	if err := b(ctx); err != nil {
		msg := fmt.Sprintf("Failed to run /bin/build: %v", err)
		var be *Error
		if errors.As(err, &be) {
			status = be.Status
			ctx.Exit(ctx.b.Failure(1), be)
		}
		ctx.Exit(ctx.b.Failure(1), Errorf(status, msg))
	}

	// Emit application metadata.
	if len(ctx.processes) > 0 {
		metadata := layers.Metadata{Processes: ctx.processes}
		if err := ctx.b.Layers.WriteApplicationMetadata(metadata); err != nil {
			ctx.Exit(ctx.b.Failure(1), Errorf(StatusInternal, "writing application metadata: %v", err))
		}
	}

	if _, err := ctx.b.Success(ctx.buildpackPlans...); err != nil {
		ctx.Exit(ctx.b.Failure(1), Errorf(StatusInternal, err.Error()))
	}

	status = StatusOk
	ctx.saveSuccessOutput(time.Since(start))
}

// Exit causes the buildpack to exit with the given exit code and message.
func (ctx *Context) Exit(exitCode int, be *Error) {
	if be != nil {
		msg := "Failure: "
		if be.ID != "" {
			msg += fmt.Sprintf("(ID: %s) ", be.ID)
		}
		msg += be.Message
		ctx.Logf(msg)
		ctx.saveErrorOutput(be)
	}

	if exitCode != 0 {
		ctx.Tipf(divider)
		ctx.Tipf(`Sorry your project couldn't be built.`)
		ctx.Tipf(`Our documentation explains ways to configure Buildpacks to better recognise your project:`)
		ctx.Tipf(` -> https://github.com/GoogleCloudPlatform/buildpacks/blob/master/README.md`)
		ctx.Tipf(`If you think you've found an issue, please report it:`)
		ctx.Tipf(` -> https://github.com/GoogleCloudPlatform/buildpacks/issues/new`)
		ctx.Tipf(divider)
	}

	ctx.exitCode = exitCode
	os.Exit(exitCode)
}

// OptOut is used during the detect phase to opt out of the build process.
func (ctx *Context) OptOut(format string, args ...interface{}) {
	ctx.Logf(format, args...)
	os.Exit(libdetect.FailStatusCode)
}

// OptIn is used during the detect phase to opt in to the build process.
func (ctx *Context) OptIn(format string, args ...interface{}) {
	ctx.Logf(format, args...)
	os.Exit(libdetect.PassStatusCode)
}

// Logf emits a structured logging line.
func (ctx *Context) Logf(format string, args ...interface{}) {
	logger.Printf(format, args...)
}

// Debugf emits a structured logging line if the debug flag is set.
func (ctx *Context) Debugf(format string, args ...interface{}) {
	if !ctx.debug {
		return
	}
	ctx.Logf("DEBUG: "+format, args...)
}

// Warnf emits a structured logging line for warnings.
func (ctx *Context) Warnf(format string, args ...interface{}) {
	ctx.Logf("Warning: "+format, args...)
}

// Tipf emits a structured logging line for usage tips.
func (ctx *Context) Tipf(format string, args ...interface{}) {
	// Tips are only displayed for the gcp/base builder, not in GAE/GCF environments.
	if os.Getenv("CNB_STACK_ID") == "google" {
		ctx.Logf(format, args...)
	}
}

// CacheHit records a cache hit debug message. This is used in acceptance test validation.
func (ctx *Context) CacheHit(tag string) {
	ctx.Debugf("%s %q", cacheHitMessage, tag)
}

// CacheMiss records a cache miss debug message. This is used in acceptance test validation.
func (ctx *Context) CacheMiss(tag string) {
	ctx.Debugf("%s %q", cacheMissMessage, tag)
}

// Span emits a structured Stackdriver span.
func (ctx *Context) Span(label string, start time.Time, status Status) {
	now := time.Now()
	attributes := map[string]interface{}{
		"/buildpack_id":      ctx.BuildpackID(),
		"/buildpack_name":    ctx.BuildpackName(),
		"/buildpack_version": ctx.BuildpackVersion(),
	}
	si, err := newSpanInfo(label, start, now, attributes, status)
	if err != nil {
		ctx.Logf("Warning: invalid span dropped: %v", err)
	}
	ctx.stats.spans = append(ctx.stats.spans, si)
}

// AddBuildPlanProvides adds a provided dependency to the build plan.
func (ctx *Context) AddBuildPlanProvides(provided buildplan.Provided) {
	ctx.buildPlan.Provides = append(ctx.buildPlan.Provides, provided)
}

// AddBuildPlanRequires adds a required dependency to the build plan.
func (ctx *Context) AddBuildPlanRequires(required buildplan.Required) {
	ctx.buildPlan.Requires = append(ctx.buildPlan.Requires, required)
}

// AddBuildpackPlan adds a required dependency to the build plan.
func (ctx *Context) AddBuildpackPlan(plan buildpackplan.Plan) {
	ctx.buildpackPlans = append(ctx.buildpackPlans, plan)
}

// AddWebProcess adds the given command as the web start process, overwriting any previous web start process.
func (ctx *Context) AddWebProcess(cmd []string) {
	current := ctx.processes
	ctx.processes = layers.Processes{}
	for _, p := range current {
		if p.Type == "web" {
			ctx.Logf("Warning: overwriting existing web process %q.", p.Command)
			continue // Do not add this item back to the ctx.processes; we are overwriting it.
		}
		ctx.processes = append(ctx.processes, p)
	}
	p := layers.Process{
		Type:    "web",
		Command: cmd[0],
		Direct:  true, // Uses Exec (no shell).
	}
	if len(cmd) > 1 {
		p.Args = cmd[1:]
	}
	ctx.processes = append(ctx.processes, p)
}

// HTTPStatus returns the status code for a url.
func (ctx *Context) HTTPStatus(url string) int {
	result := ctx.Exec([]string{"curl", "--head", "-w", "%{http_code}", "-o", "/dev/null", "--silent", "--location", "--retry", "3", url})
	cs := strings.TrimSpace(result.Stdout)
	c, err := strconv.Atoi(cs)
	if err != nil {
		ctx.Exit(1, UserErrorf("Unexpected response code %q from %s.", cs, url))
	}
	return c
}
<|endoftext|>


<commit_before>// +build linux

/*
Copyright 2016 The Kubernetes 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.
*/

// Reads the pod configuration from file or a directory of files.
package config

import (
	"fmt"
	"os"
	"strings"

	"github.com/golang/glog"
	"golang.org/x/exp/inotify"

	"k8s.io/kubernetes/pkg/api/v1"
	kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)

type podEventType int

const (
	podAdd podEventType = iota
	podModify
	podDelete
)

func (s *sourceFile) watch() error {
	_, err := os.Stat(s.path)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		// Emit an update with an empty PodList to allow FileSource to be marked as seen
		s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource}
		return fmt.Errorf("path does not exist, ignoring")
	}

	w, err := inotify.NewWatcher()
	if err != nil {
		return fmt.Errorf("unable to create inotify: %v", err)
	}
	defer w.Close()

	err = w.AddWatch(s.path, inotify.IN_DELETE_SELF|inotify.IN_CREATE|inotify.IN_MOVED_TO|inotify.IN_MODIFY|inotify.IN_MOVED_FROM|inotify.IN_DELETE)
	if err != nil {
		return fmt.Errorf("unable to create inotify for path %q: %v", s.path, err)
	}

	// Reset store with config files already existing when starting
	if err := s.resetStoreFromPath(); err != nil {
		return fmt.Errorf("unable to read config path %q: %v", s.path, err)
	}

	for {
		select {
		case event := <-w.Event:
			err = s.processEvent(event)
			if err != nil {
				return fmt.Errorf("error while processing event (%+v): %v", event, err)
			}
		case err = <-w.Error:
			return fmt.Errorf("error while watching %q: %v", s.path, err)
		}
	}
}

func (s *sourceFile) processEvent(e *inotify.Event) error {
	// Ignore file start with dots
	if strings.HasPrefix(e.Name, ".") {
		glog.V(4).Infof("Ignored pod manifest: %s, because it starts with dots", e.Name)
		return nil
	}
	var eventType podEventType
	switch {
	case (e.Mask & inotify.IN_ISDIR) > 0:
		glog.V(1).Infof("Not recursing into config path %q", s.path)
		return nil
	case (e.Mask & inotify.IN_CREATE) > 0:
		eventType = podAdd
	case (e.Mask & inotify.IN_MOVED_TO) > 0:
		eventType = podAdd
	case (e.Mask & inotify.IN_MODIFY) > 0:
		eventType = podModify
	case (e.Mask & inotify.IN_DELETE) > 0:
		eventType = podDelete
	case (e.Mask & inotify.IN_MOVED_FROM) > 0:
		eventType = podDelete
	case (e.Mask & inotify.IN_DELETE_SELF) > 0:
		return fmt.Errorf("the watched path is deleted")
	default:
		// Ignore rest events
		return nil
	}

	switch eventType {
	case podAdd, podModify:
		if pod, err := s.extractFromFile(e.Name); err != nil {
			glog.Errorf("can't process config file %q: %v", e.Name, err)
		} else {
			return s.store.Add(pod)
		}
	case podDelete:
		if objKey, keyExist := s.fileKeyMapping[e.Name]; keyExist {
			pod, podExist, err := s.store.GetByKey(objKey)
			if err != nil {
				return err
			} else if !podExist {
				return fmt.Errorf("the pod with key %s doesn't exist in cache", objKey)
			} else {
				return s.store.Delete(pod)
			}
		}
	}
	return nil
}
<commit_msg>use the base filename to check if the filename starts with a dot<commit_after>// +build linux

/*
Copyright 2016 The Kubernetes 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.
*/

// Reads the pod configuration from file or a directory of files.
package config

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

	"github.com/golang/glog"
	"golang.org/x/exp/inotify"

	"k8s.io/kubernetes/pkg/api/v1"
	kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)

type podEventType int

const (
	podAdd podEventType = iota
	podModify
	podDelete
)

func (s *sourceFile) watch() error {
	_, err := os.Stat(s.path)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		// Emit an update with an empty PodList to allow FileSource to be marked as seen
		s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource}
		return fmt.Errorf("path does not exist, ignoring")
	}

	w, err := inotify.NewWatcher()
	if err != nil {
		return fmt.Errorf("unable to create inotify: %v", err)
	}
	defer w.Close()

	err = w.AddWatch(s.path, inotify.IN_DELETE_SELF|inotify.IN_CREATE|inotify.IN_MOVED_TO|inotify.IN_MODIFY|inotify.IN_MOVED_FROM|inotify.IN_DELETE)
	if err != nil {
		return fmt.Errorf("unable to create inotify for path %q: %v", s.path, err)
	}

	// Reset store with config files already existing when starting
	if err := s.resetStoreFromPath(); err != nil {
		return fmt.Errorf("unable to read config path %q: %v", s.path, err)
	}

	for {
		select {
		case event := <-w.Event:
			err = s.processEvent(event)
			if err != nil {
				return fmt.Errorf("error while processing event (%+v): %v", event, err)
			}
		case err = <-w.Error:
			return fmt.Errorf("error while watching %q: %v", s.path, err)
		}
	}
}

func (s *sourceFile) processEvent(e *inotify.Event) error {
	// Ignore file start with dots
	if strings.HasPrefix(filepath.Base(e.Name), ".") {
		glog.V(4).Infof("Ignored pod manifest: %s, because it starts with dots", e.Name)
		return nil
	}
	var eventType podEventType
	switch {
	case (e.Mask & inotify.IN_ISDIR) > 0:
		glog.V(1).Infof("Not recursing into config path %q", s.path)
		return nil
	case (e.Mask & inotify.IN_CREATE) > 0:
		eventType = podAdd
	case (e.Mask & inotify.IN_MOVED_TO) > 0:
		eventType = podAdd
	case (e.Mask & inotify.IN_MODIFY) > 0:
		eventType = podModify
	case (e.Mask & inotify.IN_DELETE) > 0:
		eventType = podDelete
	case (e.Mask & inotify.IN_MOVED_FROM) > 0:
		eventType = podDelete
	case (e.Mask & inotify.IN_DELETE_SELF) > 0:
		return fmt.Errorf("the watched path is deleted")
	default:
		// Ignore rest events
		return nil
	}

	switch eventType {
	case podAdd, podModify:
		if pod, err := s.extractFromFile(e.Name); err != nil {
			glog.Errorf("can't process config file %q: %v", e.Name, err)
		} else {
			return s.store.Add(pod)
		}
	case podDelete:
		if objKey, keyExist := s.fileKeyMapping[e.Name]; keyExist {
			pod, podExist, err := s.store.GetByKey(objKey)
			if err != nil {
				return err
			} else if !podExist {
				return fmt.Errorf("the pod with key %s doesn't exist in cache", objKey)
			} else {
				return s.store.Delete(pod)
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2015 Google Inc. 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 container

import (
	"fmt"
	"strings"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
)

type Version interface {
	// Compare compares two versions of the runtime. On success it returns -1
	// if the version is less than the other, 1 if it is greater than the other,
	// or 0 if they are equal.
	Compare(other string) (int, error)
	// String returns a string that represents the version.
	String() string
}

// Runtime interface defines the interfaces that should be implemented
// by a container runtime.
type Runtime interface {
	// Version returns the version information of the container runtime.
	Version() (Version, error)
	// GetPods returns a list containers group by pods. The boolean parameter
	// specifies whether the runtime returns all containers including those already
	// exited and dead containers (used for garbage collection).
	GetPods(all bool) ([]*Pod, error)
	// RunPod starts all the containers of a pod within a namespace.
	RunPod(*api.Pod, map[string]volume.Volume) error
	// KillPod kills all the containers of a pod.
	KillPod(*api.Pod) error
	// RunContainerInPod starts a container within the same namespace of a pod.
	RunContainerInPod(api.Container, *api.Pod, map[string]volume.Volume) error
	// KillContainerInPod kills a container in the pod.
	KillContainerInPod(api.Container, *api.Pod) error
	// GetPodStatus retrieves the status of the pod, including the information of
	// all containers in the pod.
	GetPodStatus(*Pod) (api.PodStatus, error)
	// GetContainers returns all containers on the node, including those are
	// not managed by kubelet. If 'all' is false, then only running containers
	// are returned.
	GetContainers(all bool) ([]*Container, error)
	// TODO(yifan): Pull/Remove images
}

// Container runner is a narrow interface to consume in the Kubelet
// before there is a full implementation of Runtime.
//
// TODO: eventually include this interface in Runtime
type ContainerRunner interface {
	RunContainer(pod *api.Pod, container *api.Container, opts *RunContainerOptions) (string, error)
}

// Pod is a group of containers, with the status of the pod.
type Pod struct {
	// The ID of the pod, which can be used to retrieve a particular pod
	// from the pod list returned by GetPods().
	ID types.UID
	// The name and namespace of the pod, which is readable by human.
	Name      string
	Namespace string
	// List of containers that belongs to this pod. It may contain only
	// running containers, or mixed with dead ones (when GetPods(true)).
	Containers []*Container
	// The status of the pod.
	// TODO(yifan): Inspect and get the statuses for all pods can be expensive,
	// maybe we want to get one pod's status at a time (e.g. GetPodStatus()
	// for the particular pod after we GetPods()).
	Status api.PodStatus
}

// Container provides the runtime information for a container, such as ID, hash,
// status of the container.
type Container struct {
	// The ID of the container, used by the container runtime to identify
	// a container.
	ID types.UID
	// The name of the container, which should be the same as specified by
	// api.Container.
	Name string
	// The image name of the container.
	Image string
	// Hash of the container, used for comparison. Optional for containers
	// not managed by kubelet.
	Hash uint64
	// The timestamp of the creation time of the container.
	// TODO(yifan): Consider to move it to api.ContainerStatus.
	Created int64
}

// RunContainerOptions specify the options which are necessary for running containers
type RunContainerOptions struct {
	// The environment variables, they are in the form of 'key=value'.
	Envs []string
	// The mounts for the containers, they are in the form of:
	// 'hostPath:containerPath', or
	// 'hostPath:containerPath:ro', if the path read only.
	Binds []string
	// If the container has specified the TerminationMessagePath, then
	// this directory will be used to create and mount the log file to
	// container.TerminationMessagePath
	PodContainerDir string
	// The list of DNS servers for the container to use.
	DNS []string
	// The list of DNS search domains.
	DNSSearch []string
	// Docker namespace identifiers(currently we have 'NetMode' and 'IpcMode'.
	// These are for docker to attach a container in a pod to the pod infra
	// container's namespace.
	// TODO(yifan): Remove these after we pushed the pod infra container logic
	// into docker's container runtime.
	NetMode string
	IpcMode string
}

type Pods []*Pod

// FindPodByID finds and returns a pod in the pod list by UID. It will return an empty pod
// if not found.
func (p Pods) FindPodByID(podUID types.UID) Pod {
	for i := range p {
		if p[i].ID == podUID {
			return *p[i]
		}
	}
	return Pod{}
}

// FindPodByFullName finds and returns a pod in the pod list by the full name.
// It will return an empty pod if not found.
func (p Pods) FindPodByFullName(podFullName string) Pod {
	for i := range p {
		if BuildPodFullName(p[i].Name, p[i].Namespace) == podFullName {
			return *p[i]
		}
	}
	return Pod{}
}

// FindPod combines FindPodByID and FindPodByFullName, it finds and returns a pod in the
// pod list either by the full name or the pod ID. It will return an empty pod
// if not found.
func (p Pods) FindPod(podFullName string, podUID types.UID) Pod {
	if len(podFullName) > 0 {
		return p.FindPodByFullName(podFullName)
	}
	return p.FindPodByID(podUID)
}

// FindContainerByName returns a container in the pod with the given name.
// When there are multiple containers with the same name, the first match will
// be returned.
func (p *Pod) FindContainerByName(containerName string) *Container {
	for _, c := range p.Containers {
		if c.Name == containerName {
			return c
		}
	}
	return nil
}

// GetPodFullName returns a name that uniquely identifies a pod.
func GetPodFullName(pod *api.Pod) string {
	// Use underscore as the delimiter because it is not allowed in pod name
	// (DNS subdomain format), while allowed in the container name format.
	return fmt.Sprintf("%s_%s", pod.Name, pod.Namespace)
}

// Build the pod full name from pod name and namespace.
func BuildPodFullName(name, namespace string) string {
	return name + "_" + namespace
}

// Parse the pod full name.
func ParsePodFullName(podFullName string) (string, string, error) {
	parts := strings.Split(podFullName, "_")
	if len(parts) != 2 {
		return "", "", fmt.Errorf("failed to parse the pod full name %q", podFullName)
	}
	return parts[0], parts[1], nil
}
<commit_msg>Add RunInContainer/ExecInContainer to container Runtime API.<commit_after>/*
Copyright 2015 Google Inc. 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 container

import (
	"fmt"
	"io"
	"strings"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
)

type Version interface {
	// Compare compares two versions of the runtime. On success it returns -1
	// if the version is less than the other, 1 if it is greater than the other,
	// or 0 if they are equal.
	Compare(other string) (int, error)
	// String returns a string that represents the version.
	String() string
}

// Runtime interface defines the interfaces that should be implemented
// by a container runtime.
type Runtime interface {
	// Version returns the version information of the container runtime.
	Version() (Version, error)
	// GetPods returns a list containers group by pods. The boolean parameter
	// specifies whether the runtime returns all containers including those already
	// exited and dead containers (used for garbage collection).
	GetPods(all bool) ([]*Pod, error)
	// RunPod starts all the containers of a pod within a namespace.
	RunPod(*api.Pod, map[string]volume.Volume) error
	// KillPod kills all the containers of a pod.
	KillPod(*api.Pod) error
	// RunContainerInPod starts a container within the same namespace of a pod.
	RunContainerInPod(api.Container, *api.Pod, map[string]volume.Volume) error
	// KillContainerInPod kills a container in the pod.
	KillContainerInPod(api.Container, *api.Pod) error
	// GetPodStatus retrieves the status of the pod, including the information of
	// all containers in the pod.
	GetPodStatus(*Pod) (api.PodStatus, error)
	// GetContainers returns all containers on the node, including those are
	// not managed by kubelet. If 'all' is false, then only running containers
	// are returned.
	GetContainers(all bool) ([]*Container, error)
	// TODO(vmarmol): Merge RunInContainer and ExecInContainer.
	// Runs the command in the container of the specified pod using nsinit.
	RunInContainer(container api.Container, pod *api.Pod, cmd []string)
	// Runs the command in the container of the specified pod using nsenter.
	// Attaches the processes stdin, stdout, and stderr. Optionally uses a
	// tty.
	ExecInContainer(container api.Container, pod *api.Pod, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool)
	// TODO(yifan): Pull/Remove images
}

// Container runner is a narrow interface to consume in the Kubelet
// before there is a full implementation of Runtime.
//
// TODO: eventually include this interface in Runtime
type ContainerRunner interface {
	RunContainer(pod *api.Pod, container *api.Container, opts *RunContainerOptions) (string, error)
}

// Pod is a group of containers, with the status of the pod.
type Pod struct {
	// The ID of the pod, which can be used to retrieve a particular pod
	// from the pod list returned by GetPods().
	ID types.UID
	// The name and namespace of the pod, which is readable by human.
	Name      string
	Namespace string
	// List of containers that belongs to this pod. It may contain only
	// running containers, or mixed with dead ones (when GetPods(true)).
	Containers []*Container
	// The status of the pod.
	// TODO(yifan): Inspect and get the statuses for all pods can be expensive,
	// maybe we want to get one pod's status at a time (e.g. GetPodStatus()
	// for the particular pod after we GetPods()).
	Status api.PodStatus
}

// Container provides the runtime information for a container, such as ID, hash,
// status of the container.
type Container struct {
	// The ID of the container, used by the container runtime to identify
	// a container.
	ID types.UID
	// The name of the container, which should be the same as specified by
	// api.Container.
	Name string
	// The image name of the container.
	Image string
	// Hash of the container, used for comparison. Optional for containers
	// not managed by kubelet.
	Hash uint64
	// The timestamp of the creation time of the container.
	// TODO(yifan): Consider to move it to api.ContainerStatus.
	Created int64
}

// RunContainerOptions specify the options which are necessary for running containers
type RunContainerOptions struct {
	// The environment variables, they are in the form of 'key=value'.
	Envs []string
	// The mounts for the containers, they are in the form of:
	// 'hostPath:containerPath', or
	// 'hostPath:containerPath:ro', if the path read only.
	Binds []string
	// If the container has specified the TerminationMessagePath, then
	// this directory will be used to create and mount the log file to
	// container.TerminationMessagePath
	PodContainerDir string
	// The list of DNS servers for the container to use.
	DNS []string
	// The list of DNS search domains.
	DNSSearch []string
	// Docker namespace identifiers(currently we have 'NetMode' and 'IpcMode'.
	// These are for docker to attach a container in a pod to the pod infra
	// container's namespace.
	// TODO(yifan): Remove these after we pushed the pod infra container logic
	// into docker's container runtime.
	NetMode string
	IpcMode string
}

type Pods []*Pod

// FindPodByID finds and returns a pod in the pod list by UID. It will return an empty pod
// if not found.
func (p Pods) FindPodByID(podUID types.UID) Pod {
	for i := range p {
		if p[i].ID == podUID {
			return *p[i]
		}
	}
	return Pod{}
}

// FindPodByFullName finds and returns a pod in the pod list by the full name.
// It will return an empty pod if not found.
func (p Pods) FindPodByFullName(podFullName string) Pod {
	for i := range p {
		if BuildPodFullName(p[i].Name, p[i].Namespace) == podFullName {
			return *p[i]
		}
	}
	return Pod{}
}

// FindPod combines FindPodByID and FindPodByFullName, it finds and returns a pod in the
// pod list either by the full name or the pod ID. It will return an empty pod
// if not found.
func (p Pods) FindPod(podFullName string, podUID types.UID) Pod {
	if len(podFullName) > 0 {
		return p.FindPodByFullName(podFullName)
	}
	return p.FindPodByID(podUID)
}

// FindContainerByName returns a container in the pod with the given name.
// When there are multiple containers with the same name, the first match will
// be returned.
func (p *Pod) FindContainerByName(containerName string) *Container {
	for _, c := range p.Containers {
		if c.Name == containerName {
			return c
		}
	}
	return nil
}

// GetPodFullName returns a name that uniquely identifies a pod.
func GetPodFullName(pod *api.Pod) string {
	// Use underscore as the delimiter because it is not allowed in pod name
	// (DNS subdomain format), while allowed in the container name format.
	return fmt.Sprintf("%s_%s", pod.Name, pod.Namespace)
}

// Build the pod full name from pod name and namespace.
func BuildPodFullName(name, namespace string) string {
	return name + "_" + namespace
}

// Parse the pod full name.
func ParsePodFullName(podFullName string) (string, string, error) {
	parts := strings.Split(podFullName, "_")
	if len(parts) != 2 {
		return "", "", fmt.Errorf("failed to parse the pod full name %q", podFullName)
	}
	return parts[0], parts[1], nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes 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 readiness

import (
	negtypes "k8s.io/ingress-gce/pkg/neg/types"
	"net"
	"testing"
)

func newFakePoller() *poller {
	reflector := newTestReadinessReflector(fakeContext())
	return reflector.poller
}

func TestPollerEndpointRegistrationAndScanForWork(t *testing.T) {
	poller := newFakePoller()
	podLister := poller.podLister
	fakeLookup := poller.lookup.(*fakeLookUp)
	namespace := "ns"
	name := "svc"
	port := int32(80)
	targetPort1 := "8080"
	targetPort2 := "namedport"
	syncerKey1 := negtypes.NegSyncerKey{
		Namespace:  namespace,
		Name:       name,
		Port:       port,
		TargetPort: targetPort1,
	}
	syncerKey2 := negtypes.NegSyncerKey{
		Namespace:  namespace,
		Name:       name,
		Port:       port,
		TargetPort: targetPort2,
	}
	zone1 := "zone1"
	zone2 := "zone2"
	name1 := "neg1"
	name2 := "neg2"
	neg1 := negMeta{
		SyncerKey: syncerKey1,
		Zone:      zone1,
		Name:      name1,
	}
	neg2 := negMeta{
		SyncerKey: syncerKey1,
		Zone:      zone2,
		Name:      name1,
	}
	neg3 := negMeta{
		SyncerKey: syncerKey2,
		Zone:      zone1,
		Name:      name2,
	}
	neg4 := negMeta{
		SyncerKey: syncerKey2,
		Zone:      zone2,
		Name:      name2,
	}

	for _, tc := range []struct {
		desc                string
		key                 negMeta
		inputMap            negtypes.EndpointPodMap
		mutateState         func()
		expectWork          map[negMeta]bool
		expectEndpointCount int
	}{
		{
			desc:        "empty input",
			key:         negMeta{},
			inputMap:    negtypes.EndpointPodMap{},
			mutateState: func() {},
			expectWork:  map[negMeta]bool{},
		},
		{
			desc:     "empty input 1",
			key:      neg2,
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
			},
			expectWork: map[negMeta]bool{},
		},
		{
			desc:     "empty input 2",
			key:      neg3,
			inputMap: generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = false
				endpointMap := generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork: map[negMeta]bool{},
		},
		{
			desc:     "empty input 3",
			key:      neg4,
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
			},
			expectWork: map[negMeta]bool{},
		},
		{
			desc:     "add endpoint for neg1",
			key:      neg1,
			inputMap: generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
			},
			expectEndpointCount: 10,
		},
		{
			desc:     "add endpoint for neg2",
			key:      neg2,
			inputMap: generateEndpointMap(net.ParseIP("1.1.2.1"), 10, instance2, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.2.1"), 10, instance2, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
			},
			expectEndpointCount: 10,
		},
		{
			desc:     "add endpoint for neg3, half of endpoint is already ready",
			key:      neg3,
			inputMap: generateEndpointMap(net.ParseIP("1.1.3.1"), 10, instance3, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.3.1"), 10, instance3, "8080")
				count := 0
				for _, v := range endpointMap {
					count++
					var negConditionTrue bool
					if count > 5 {
						negConditionTrue = true
					}
					podLister.Add(generatePod(testNamespace, v.Name, true, negConditionTrue, negConditionTrue))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
				neg3: true,
			},
			expectEndpointCount: 5,
		},
		{
			desc:     "add endpoint for neg4, half of endpoint does not have neg readiness gate",
			key:      neg4,
			inputMap: generateEndpointMap(net.ParseIP("1.1.4.1"), 10, instance4, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.4.1"), 10, instance4, "8080")
				count := 0
				for _, v := range endpointMap {
					count++
					var negReadinessGate bool
					if count > 5 {
						negReadinessGate = true
					}
					podLister.Add(generatePod(testNamespace, v.Name, negReadinessGate, false, false))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
				neg3: true,
				neg4: true,
			},
			expectEndpointCount: 5,
		},
		{
			desc:     "change endpoints for neg1",
			key:      neg1,
			inputMap: generateEndpointMap(net.ParseIP("1.1.2.1"), 5, instance2, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.2.1"), 5, instance2, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
				neg3: true,
				neg4: true,
			},
			expectEndpointCount: 5,
		},
		{
			desc:     "mark neg3, neg4 for polling",
			key:      negMeta{},
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				poller.markPolling(neg3)
				poller.markPolling(neg4)
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
			},
		},
		{
			desc:     "mark neg1, neg2 for polling",
			key:      negMeta{},
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				poller.markPolling(neg1)
				poller.markPolling(neg2)
			},
			expectWork: map[negMeta]bool{},
		},
		{
			desc:     "unmark neg1 polling",
			key:      negMeta{},
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				poller.unMarkPolling(neg1)

			},
			expectWork: map[negMeta]bool{
				neg1: true,
			},
		},
		{
			desc:     "no longer need readiness gate for neg1",
			key:      neg1,
			inputMap: generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = false
				endpointMap := generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork:          map[negMeta]bool{},
			expectEndpointCount: 0,
		},
	} {
		tc.mutateState()
		poller.RegisterNegEndpoints(tc.key, tc.inputMap)
		ret := poller.ScanForWork()
		if len(ret) != len(tc.expectWork) {
			t.Errorf("For test case %q, expect %v, got: %v", tc.desc, tc.expectWork, ret)
		}
		for _, key := range ret {
			if _, ok := tc.expectWork[key]; !ok {
				t.Errorf("For test case %q, expect work %v to not exists, but it exists", tc.desc, key)
			}
		}

		target, ok := poller.pollMap[tc.key]
		if tc.expectEndpointCount == 0 && ok {
			t.Errorf("For test case %q, expect key %v to not exists in pollMap, got: %v", tc.desc, tc.key, target)
		}
		if tc.expectEndpointCount > 0 {
			if !ok {
				t.Errorf("For test case %q, expect key %v to exists in pollMap, got nil", tc.desc, tc.key)
			}
			if ok && tc.expectEndpointCount != len(target.endpointMap) {
				t.Errorf("For test case %q, expect endpoint count %v, but got: %v", tc.desc, tc.expectEndpointCount, len(target.endpointMap))
			}
		}
	}
}
<commit_msg>add more poller test<commit_after>/*
Copyright 2019 The Kubernetes 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 readiness

import (
	"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
	"google.golang.org/api/compute/v1"
	"k8s.io/apimachinery/pkg/types"
	negtypes "k8s.io/ingress-gce/pkg/neg/types"
	"k8s.io/ingress-gce/pkg/utils"
	"net"
	"strconv"
	"testing"
)

func newFakePoller() *poller {
	reflector := newTestReadinessReflector(fakeContext())
	return reflector.poller
}

func TestPollerEndpointRegistrationAndScanForWork(t *testing.T) {
	t.Parallel()

	poller := newFakePoller()
	podLister := poller.podLister
	fakeLookup := poller.lookup.(*fakeLookUp)
	namespace := "ns"
	name := "svc"
	port := int32(80)
	targetPort1 := "8080"
	targetPort2 := "namedport"
	syncerKey1 := negtypes.NegSyncerKey{
		Namespace:  namespace,
		Name:       name,
		Port:       port,
		TargetPort: targetPort1,
	}
	syncerKey2 := negtypes.NegSyncerKey{
		Namespace:  namespace,
		Name:       name,
		Port:       port,
		TargetPort: targetPort2,
	}
	zone1 := "zone1"
	zone2 := "zone2"
	name1 := "neg1"
	name2 := "neg2"
	neg1 := negMeta{
		SyncerKey: syncerKey1,
		Zone:      zone1,
		Name:      name1,
	}
	neg2 := negMeta{
		SyncerKey: syncerKey1,
		Zone:      zone2,
		Name:      name1,
	}
	neg3 := negMeta{
		SyncerKey: syncerKey2,
		Zone:      zone1,
		Name:      name2,
	}
	neg4 := negMeta{
		SyncerKey: syncerKey2,
		Zone:      zone2,
		Name:      name2,
	}

	for _, tc := range []struct {
		desc                string
		key                 negMeta
		inputMap            negtypes.EndpointPodMap
		mutateState         func()
		expectWork          map[negMeta]bool
		expectEndpointCount int
	}{
		{
			desc:        "empty input",
			key:         negMeta{},
			inputMap:    negtypes.EndpointPodMap{},
			mutateState: func() {},
			expectWork:  map[negMeta]bool{},
		},
		{
			desc:     "empty input 1",
			key:      neg2,
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
			},
			expectWork: map[negMeta]bool{},
		},
		{
			desc:     "empty input 2",
			key:      neg3,
			inputMap: generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = false
				endpointMap := generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork: map[negMeta]bool{},
		},
		{
			desc:     "empty input 3",
			key:      neg4,
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
			},
			expectWork: map[negMeta]bool{},
		},
		{
			desc:     "add endpoint for neg1",
			key:      neg1,
			inputMap: generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
			},
			expectEndpointCount: 10,
		},
		{
			desc:     "add endpoint for neg2",
			key:      neg2,
			inputMap: generateEndpointMap(net.ParseIP("1.1.2.1"), 10, instance2, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.2.1"), 10, instance2, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
			},
			expectEndpointCount: 10,
		},
		{
			desc:     "add endpoint for neg3, half of endpoint is already ready",
			key:      neg3,
			inputMap: generateEndpointMap(net.ParseIP("1.1.3.1"), 10, instance3, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.3.1"), 10, instance3, "8080")
				count := 0
				for _, v := range endpointMap {
					count++
					var negConditionTrue bool
					if count > 5 {
						negConditionTrue = true
					}
					podLister.Add(generatePod(testNamespace, v.Name, true, negConditionTrue, negConditionTrue))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
				neg3: true,
			},
			expectEndpointCount: 5,
		},
		{
			desc:     "add endpoint for neg4, half of endpoint does not have neg readiness gate",
			key:      neg4,
			inputMap: generateEndpointMap(net.ParseIP("1.1.4.1"), 10, instance4, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.4.1"), 10, instance4, "8080")
				count := 0
				for _, v := range endpointMap {
					count++
					var negReadinessGate bool
					if count > 5 {
						negReadinessGate = true
					}
					podLister.Add(generatePod(testNamespace, v.Name, negReadinessGate, false, false))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
				neg3: true,
				neg4: true,
			},
			expectEndpointCount: 5,
		},
		{
			desc:     "change endpoints for neg1",
			key:      neg1,
			inputMap: generateEndpointMap(net.ParseIP("1.1.2.1"), 5, instance2, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = true
				endpointMap := generateEndpointMap(net.ParseIP("1.1.2.1"), 5, instance2, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
				neg3: true,
				neg4: true,
			},
			expectEndpointCount: 5,
		},
		{
			desc:     "mark neg3, neg4 for polling",
			key:      negMeta{},
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				poller.markPolling(neg3)
				poller.markPolling(neg4)
			},
			expectWork: map[negMeta]bool{
				neg1: true,
				neg2: true,
			},
		},
		{
			desc:     "mark neg1, neg2 for polling",
			key:      negMeta{},
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				poller.markPolling(neg1)
				poller.markPolling(neg2)
			},
			expectWork: map[negMeta]bool{},
		},
		{
			desc:     "unmark neg1 polling",
			key:      negMeta{},
			inputMap: negtypes.EndpointPodMap{},
			mutateState: func() {
				poller.unMarkPolling(neg1)

			},
			expectWork: map[negMeta]bool{
				neg1: true,
			},
		},
		{
			desc:     "no longer need readiness gate for neg1",
			key:      neg1,
			inputMap: generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080"),
			mutateState: func() {
				fakeLookup.readinessGateEnabled = false
				endpointMap := generateEndpointMap(net.ParseIP("1.1.1.1"), 10, instance1, "8080")
				for _, v := range endpointMap {
					podLister.Add(generatePod(testNamespace, v.Name, true, false, false))
				}
			},
			expectWork:          map[negMeta]bool{},
			expectEndpointCount: 0,
		},
	} {
		tc.mutateState()
		poller.RegisterNegEndpoints(tc.key, tc.inputMap)
		ret := poller.ScanForWork()
		if len(ret) != len(tc.expectWork) {
			t.Errorf("For test case %q, expect %v, got: %v", tc.desc, tc.expectWork, ret)
		}
		for _, key := range ret {
			if _, ok := tc.expectWork[key]; !ok {
				t.Errorf("For test case %q, expect work %v to not exists, but it exists", tc.desc, key)
			}
		}

		target, ok := poller.pollMap[tc.key]
		if tc.expectEndpointCount == 0 && ok {
			t.Errorf("For test case %q, expect key %v to not exists in pollMap, got: %v", tc.desc, tc.key, target)
		}
		if tc.expectEndpointCount > 0 {
			if !ok {
				t.Errorf("For test case %q, expect key %v to exists in pollMap, got nil", tc.desc, tc.key)
			}
			if ok && tc.expectEndpointCount != len(target.endpointMap) {
				t.Errorf("For test case %q, expect endpoint count %v, but got: %v", tc.desc, tc.expectEndpointCount, len(target.endpointMap))
			}
		}
	}
}

func TestPoll(t *testing.T) {
	t.Parallel()

	poller := newFakePoller()
	negCloud := poller.negCloud
	namer := utils.NewNamer("clusteruid", "")

	ns := "ns"
	podName := "pod1"
	negName := namer.NEG(ns, "svc", int32(80))
	zone := "us-central1-b"
	key := negMeta{
		SyncerKey: negtypes.NegSyncerKey{},
		Name:      negName,
		Zone:      zone,
	}
	ip := "10.1.2.3"
	port := int64(80)
	instance := "k8s-node-xxxxxx"

	// mark polling to true
	poller.pollMap[key] = &pollTarget{
		endpointMap: negtypes.EndpointPodMap{
			negtypes.NetworkEndpoint{IP: ip, Port: strconv.FormatInt(port, 10), Node: instance}: types.NamespacedName{Namespace: ns, Name: podName},
		},
		polling: true,
	}

	retry, err := poller.Poll(key)
	if err != nil {
		t.Errorf("Does not expect err, but got %v", err)
	}
	if retry != true {
		t.Errorf("Expect retry = true, but got %v", retry)
	}

	// unmark polling
	poller.pollMap[key].polling = false
	retry, err = poller.Poll(key)
	// expect NEG not exist error
	if err == nil {
		t.Errorf("Expect err, but got %v", err)
	}
	if retry != true {
		t.Errorf("Expect retry = true, but got %v", retry)
	}

	// create NEG, but with no endpoint
	negCloud.CreateNetworkEndpointGroup(&compute.NetworkEndpointGroup{Name: negName, Zone: zone}, zone)
	retry, err = poller.Poll(key)
	if err != nil {
		t.Errorf("Does not expect err, but got %v", err)
	}
	if retry != true {
		t.Errorf("Expect retry = true, but got %v", retry)
	}

	// add NE to the NEG, but NE not healthy
	ne := &compute.NetworkEndpoint{
		IpAddress: ip,
		Port:      port,
		Instance:  instance,
	}
	negCloud.AttachNetworkEndpoints(negName, zone, []*compute.NetworkEndpoint{ne})
	retry, err = poller.Poll(key)
	if err != nil {
		t.Errorf("Does not expect err, but got %v", err)
	}
	if retry != true {
		t.Errorf("Expect retry = true, but got %v", retry)
	}

	// add NE with healthy status
	negtypes.GetNetworkEndpointStore(negCloud).AddNetworkEndpointHealthStatus(*meta.ZonalKey(negName, zone), negtypes.NetworkEndpointEntry{
		NetworkEndpoint: ne,
		Healths: []*compute.HealthStatusForNetworkEndpoint{
			{
				BackendService: &compute.BackendServiceReference{
					BackendService: negName,
				},
				HealthState: healthyState,
			},
		},
	})
	retry, err = poller.Poll(key)
	if err != nil {
		t.Errorf("Does not expect err, but got %v", err)
	}
	if retry != false {
		t.Errorf("Expect retry = false, but got %v", retry)
	}
}
<|endoftext|>