From aa8a711ca7000c18a47f84159aa0f604c65c4909 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Fri, 25 Mar 2022 09:57:46 -0400 Subject: [PATCH] Create multi-runtime capability Better define the error when a function is not found, and use this to support multiple engines. --- pkg/fn/eval.go | 10 +++++ pkg/fn/eval_test.go | 43 ++++++++++++++++++++ pkg/fn/multiruntime.go | 55 ++++++++++++++++++++++++++ porch/engine/pkg/engine/grpcruntime.go | 1 + porch/engine/pkg/kpt/eval.go | 3 +- 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 pkg/fn/eval_test.go create mode 100644 pkg/fn/multiruntime.go diff --git a/pkg/fn/eval.go b/pkg/fn/eval.go index 643e458060..c892c9c7ef 100644 --- a/pkg/fn/eval.go +++ b/pkg/fn/eval.go @@ -16,6 +16,7 @@ package fn import ( "context" + "fmt" "io" v1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" @@ -28,6 +29,15 @@ type FunctionRunner interface { } // FunctionRuntime provides a way to obtain a function runner to be used for a given function configuration. +// If the function is not found, this should return an error that includes a NotFoundError in the chain. type FunctionRuntime interface { GetRunner(ctx context.Context, fn *v1.Function) (FunctionRunner, error) } + +type NotFoundError struct { + Function v1.Function +} + +func (e *NotFoundError) Error() string { + return fmt.Sprintf("function %q not found", e.Function.Image) +} diff --git a/pkg/fn/eval_test.go b/pkg/fn/eval_test.go new file mode 100644 index 0000000000..29de7097ad --- /dev/null +++ b/pkg/fn/eval_test.go @@ -0,0 +1,43 @@ +// Copyright 2022 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 fn + +import ( + "errors" + "reflect" + "testing" + + v1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" +) + +func TestNotFound(t *testing.T) { + var err error + + fn := &v1.Function{Image: "foo"} + err = &NotFoundError{Function: *fn} + + var notFoundErr *NotFoundError + if !errors.As(err, ¬FoundErr) { + t.Fatalf("expected NotFoundError to satisfy errors.As") + } + + if got, want := notFoundErr.Function, *fn; !reflect.DeepEqual(got, want) { + t.Fatalf("function in NotFoundError did not match expected; got %#v, want %#v", got, want) + } + + if got, want := notFoundErr.Error(), "function \"foo\" not found"; !reflect.DeepEqual(got, want) { + t.Fatalf("string form NotFoundError did not match expected; got %#v, want %#v", got, want) + } +} diff --git a/pkg/fn/multiruntime.go b/pkg/fn/multiruntime.go new file mode 100644 index 0000000000..e464ebbcb3 --- /dev/null +++ b/pkg/fn/multiruntime.go @@ -0,0 +1,55 @@ +// Copyright 2022 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 fn + +import ( + "context" + "errors" + + v1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" +) + +// MultiRuntime is a compound FunctionRuntime that will use the first available runner. +type MultiRuntime struct { + runtimes []FunctionRuntime +} + +// NewMultiRuntime builds a MultiRuntime. +func NewMultiRuntime(runtimes []FunctionRuntime) *MultiRuntime { + return &MultiRuntime{ + runtimes: runtimes, + } +} + +var _ FunctionRuntime = &MultiRuntime{} + +// GetRunner implements FunctionRuntime +func (r *MultiRuntime) GetRunner(ctx context.Context, fn *v1.Function) (FunctionRunner, error) { + for _, runtime := range r.runtimes { + runner, err := runtime.GetRunner(ctx, fn) + if err != nil { + var notFoundError *NotFoundError + if errors.As(err, ¬FoundError) { + // maybe another runtime + } else { + return nil, err + } + } else { + return runner, nil + } + } + + return nil, &NotFoundError{Function: *fn} +} diff --git a/porch/engine/pkg/engine/grpcruntime.go b/porch/engine/pkg/engine/grpcruntime.go index 9f1be05c7c..db1c09653b 100644 --- a/porch/engine/pkg/engine/grpcruntime.go +++ b/porch/engine/pkg/engine/grpcruntime.go @@ -36,6 +36,7 @@ type grpcRuntime struct { var _ kpt.FunctionRuntime = &grpcRuntime{} func (gr *grpcRuntime) GetRunner(ctx context.Context, fn *v1.Function) (fn.FunctionRunner, error) { + // TODO: Check if the function is actually available? return &grpcRunner{ ctx: ctx, client: gr.client, diff --git a/porch/engine/pkg/kpt/eval.go b/porch/engine/pkg/kpt/eval.go index 0313983baa..bea6fbc332 100644 --- a/porch/engine/pkg/kpt/eval.go +++ b/porch/engine/pkg/kpt/eval.go @@ -16,7 +16,6 @@ package kpt import ( "context" - "fmt" "io" kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" @@ -39,7 +38,7 @@ var _ FunctionRuntime = &runtime{} func (e *runtime) GetRunner(ctx context.Context, fn *kptfilev1.Function) (fn.FunctionRunner, error) { processor := internal.FindProcessor(fn.Image) if processor == nil { - return nil, fmt.Errorf("unsupported kpt function %q", fn.Image) + return nil, &fn.NotFoundError{Function: *fn} } return &runner{