From 3891e964fb88cd6b8928fd6c126a9128d9afbb7a Mon Sep 17 00:00:00 2001 From: Dayuan Date: Mon, 11 Mar 2024 19:40:24 +0800 Subject: [PATCH] refactor: opsrule with module framework (#57) --- models/generators/generator.go | 124 ---------------------- models/generators/go.mod | 20 ++-- models/generators/opsrule/opsrule.go | 39 +++---- models/generators/opsrule/opsrule_test.go | 69 ++++++------ 4 files changed, 57 insertions(+), 195 deletions(-) delete mode 100644 models/generators/generator.go diff --git a/models/generators/generator.go b/models/generators/generator.go deleted file mode 100644 index c45a2c6..0000000 --- a/models/generators/generator.go +++ /dev/null @@ -1,124 +0,0 @@ -package generators - -import ( - "fmt" - - "github.com/hashicorp/go-plugin" - "gopkg.in/yaml.v2" - "k8s.io/apimachinery/pkg/runtime" - v1 "kusionstack.io/kusion/pkg/apis/core/v1" - "kusionstack.io/kusion/pkg/apis/core/v1/workload" - "kusionstack.io/kusion/pkg/log" - "kusionstack.io/kusion/pkg/modules" - "kusionstack.io/kusion/pkg/modules/proto" -) - -// HandshakeConfig is a common handshake that is shared by plugin and host. -var HandshakeConfig = plugin.HandshakeConfig{ - ProtocolVersion: 1, - MagicCookieKey: "MODULE_PLUGIN", - MagicCookieValue: "ON", -} - -func StartModule(module modules.Module) { - plugin.Serve(&plugin.ServeConfig{ - HandshakeConfig: HandshakeConfig, - Plugins: map[string]plugin.Plugin{ - modules.PluginKey: &modules.GRPCPlugin{Impl: module}, - }, - - // A non-nil value here enables gRPC serving for this plugin... - GRPCServer: plugin.DefaultGRPCServer, - }) -} - -type GeneratorRequest struct { - // Project represents the project name - Project string `json:"project,omitempty"` - // Stack represents the stack name - Stack string `json:"stack,omitempty"` - // App represents the application name, which is typically the same as the namespace of Kubernetes resources - App string `json:"app,omitempty"` - // Workload represents the workload configuration - Workload *workload.Workload `json:"workload,omitempty"` - // DevModuleConfig is the developer's inputs of this module - DevModuleConfig v1.Accessory `json:"dev_module_config,omitempty"` - // PlatformModuleConfig is the platform engineer's inputs of this module - PlatformModuleConfig v1.GenericConfig `json:"platform_module_config,omitempty"` - // RuntimeConfig is the runtime configurations defined in the workspace config - RuntimeConfig *v1.RuntimeConfigs `json:"runtime_config,omitempty"` -} - -func NewGeneratorRequest(req *proto.GeneratorRequest) (*GeneratorRequest, error) { - - log.Infof("module proto request received:%s", req.String()) - - // validate workload - if req.Workload == nil { - return nil, fmt.Errorf("workload in the request is nil") - } - w := &workload.Workload{} - if err := yaml.Unmarshal(req.Workload, w); err != nil { - return nil, fmt.Errorf("unmarshal workload failed. %w", err) - } - - var dc v1.Accessory - if req.DevModuleConfig != nil { - if err := yaml.Unmarshal(req.DevModuleConfig, &dc); err != nil { - return nil, fmt.Errorf("unmarshal dev module config failed. %w", err) - } - } - - var pc v1.GenericConfig - if req.PlatformModuleConfig != nil { - if err := yaml.Unmarshal(req.PlatformModuleConfig, &pc); err != nil { - return nil, fmt.Errorf("unmarshal platform module config failed. %w", err) - } - } - - var rc *v1.RuntimeConfigs - if req.RuntimeConfig != nil { - if err := yaml.Unmarshal(req.RuntimeConfig, rc); err != nil { - return nil, fmt.Errorf("unmarshal runtime config failed. %w", err) - } - } - - result := &GeneratorRequest{ - Project: req.Project, - Stack: req.Stack, - App: req.App, - Workload: w, - DevModuleConfig: dc, - PlatformModuleConfig: pc, - RuntimeConfig: rc, - } - out, err := yaml.Marshal(result) - if err != nil { - return nil, fmt.Errorf("marshal new generator request failed. %w", err) - } - log.Infof("new generator request:%s", string(out)) - return result, nil -} - -func EmptyResponse() *proto.GeneratorResponse { - return &proto.GeneratorResponse{} -} - -func WrapK8sResourceToKusionResource(id string, resource any) (*v1.Resource, error) { - gvk := resource.(runtime.Object).GetObjectKind().GroupVersionKind().String() - - // fixme: this function converts int to int64 by default - unstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource) - if err != nil { - return nil, err - } - return &v1.Resource{ - ID: id, - Type: v1.Kubernetes, - Attributes: unstructured, - DependsOn: nil, - Extensions: map[string]any{ - v1.ResourceExtensionGVK: gvk, - }, - }, nil -} diff --git a/models/generators/go.mod b/models/generators/go.mod index 29da174..165a8c0 100644 --- a/models/generators/go.mod +++ b/models/generators/go.mod @@ -1,13 +1,12 @@ module kusion-modules -go 1.19 +go 1.22 require ( - github.com/hashicorp/go-plugin v1.6.0 - gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.27.2 kusionstack.io/kube-api v0.1.1 - kusionstack.io/kusion v0.10.1-0.20240305085131-a947ad87dfa8 + kusionstack.io/kusion v0.10.1-0.20240311030125-729b89bf8197 + kusionstack.io/kusion-module-framework v0.0.0-20240311081739-579272fe615f ) require ( @@ -17,13 +16,13 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/hashicorp/go-hclog v0.16.2 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect - github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect @@ -35,12 +34,13 @@ require ( golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/grpc v1.61.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.27.2 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect diff --git a/models/generators/opsrule/opsrule.go b/models/generators/opsrule/opsrule.go index 7a312fe..3b7b025 100644 --- a/models/generators/opsrule/opsrule.go +++ b/models/generators/opsrule/opsrule.go @@ -1,41 +1,34 @@ package main import ( + "context" "errors" "strconv" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "kusionstack.io/kube-api/apps/v1alpha1" + "kusionstack.io/kusion-module-framework/pkg/module" + "kusionstack.io/kusion-module-framework/pkg/server" v1 "kusionstack.io/kusion/pkg/apis/core/v1" "kusionstack.io/kusion/pkg/apis/core/v1/workload" "kusionstack.io/kusion/pkg/log" - "kusionstack.io/kusion/pkg/modules" - "kusionstack.io/kusion/pkg/modules/proto" - jsonutil "kusionstack.io/kusion/pkg/util/json" - - generators "kusion-modules" ) type OpsRuleModule struct{} -func (o *OpsRuleModule) Generate(r *proto.GeneratorRequest) (*proto.GeneratorResponse, error) { - emptyResponse := generators.EmptyResponse() - request, err := generators.NewGeneratorRequest(r) - if err != nil { - return nil, err - } +func (o *OpsRuleModule) Generate(_ context.Context, request *module.GeneratorRequest) (*module.GeneratorResponse, error) { // opsRule does not exist in AppConfig and workspace config if request.DevModuleConfig == nil && request.PlatformModuleConfig == nil { log.Info("OpsRule does not exist in AppConfig and workspace config") - return emptyResponse, nil + return nil, nil } // Job does not support maxUnavailable if request.Workload.Header.Type == workload.TypeJob { log.Infof("Job does not support opsRule") - return emptyResponse, nil + return nil, nil } if request.Workload.Service.Type == workload.Collaset { @@ -49,12 +42,12 @@ func (o *OpsRuleModule) Generate(r *proto.GeneratorRequest) (*proto.GeneratorRes Kind: "PodTransitionRule", }, ObjectMeta: metav1.ObjectMeta{ - Name: modules.UniqueAppName(request.Project, request.Stack, request.App), - Namespace: request.App, + Name: module.UniqueAppName(request.Project, request.Stack, request.App), + Namespace: request.Project, }, Spec: v1alpha1.PodTransitionRuleSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: modules.UniqueAppLabels(request.Project, request.App), + MatchLabels: module.UniqueAppLabels(request.Project, request.App), }, Rules: []v1alpha1.TransitionRule{ { @@ -68,18 +61,16 @@ func (o *OpsRuleModule) Generate(r *proto.GeneratorRequest) (*proto.GeneratorRes }, }, } - resourceID := modules.KubernetesResourceID(ptr.TypeMeta, ptr.ObjectMeta) - resource, err := generators.WrapK8sResourceToKusionResource(resourceID, ptr) + resourceID := module.KubernetesResourceID(ptr.TypeMeta, ptr.ObjectMeta) + resource, err := module.WrapK8sResourceToKusionResource(resourceID, ptr) if err != nil { return nil, err } - str := jsonutil.Marshal2String(resource) - b := []byte(str) - return &proto.GeneratorResponse{ - Resources: [][]byte{b}, + return &module.GeneratorResponse{ + Resources: []v1.Resource{*resource}, }, nil } - return emptyResponse, nil + return nil, nil } func GetMaxUnavailable(devConfig v1.Accessory, platformConfig v1.GenericConfig) (intstr.IntOrString, error) { @@ -114,5 +105,5 @@ func GetMaxUnavailable(devConfig v1.Accessory, platformConfig v1.GenericConfig) } func main() { - modules.StartModule(&OpsRuleModule{}) + server.Start(&OpsRuleModule{}) } diff --git a/models/generators/opsrule/opsrule_test.go b/models/generators/opsrule/opsrule_test.go index ebe9826..2437bf8 100644 --- a/models/generators/opsrule/opsrule_test.go +++ b/models/generators/opsrule/opsrule_test.go @@ -1,20 +1,19 @@ package main import ( + "context" "reflect" "testing" + "gopkg.in/yaml.v2" + "kusionstack.io/kusion-module-framework/pkg/module" v1 "kusionstack.io/kusion/pkg/apis/core/v1" "kusionstack.io/kusion/pkg/apis/core/v1/workload" - "kusionstack.io/kusion/pkg/modules/proto" - jsonutil "kusionstack.io/kusion/pkg/util/json" - - generators "kusion-modules" ) func TestOpsRuleModule_Generate(t *testing.T) { resConfig30 := v1.Resource{ - ID: "apps.kusionstack.io/v1alpha1:PodTransitionRule:foo:default-dev-foo", + ID: "apps.kusionstack.io/v1alpha1:PodTransitionRule:default:default-dev-foo", Type: "Kubernetes", Attributes: map[string]interface{}{ "apiVersion": "apps.kusionstack.io/v1alpha1", @@ -22,7 +21,7 @@ func TestOpsRuleModule_Generate(t *testing.T) { "metadata": map[string]interface{}{ "creationTimestamp": interface{}(nil), "name": "default-dev-foo", - "namespace": "foo", + "namespace": "default", }, "spec": map[string]interface{}{ "rules": []interface{}{map[string]interface{}{ @@ -44,7 +43,7 @@ func TestOpsRuleModule_Generate(t *testing.T) { }, } resConfig40 := v1.Resource{ - ID: "apps.kusionstack.io/v1alpha1:PodTransitionRule:foo:default-dev-foo", + ID: "apps.kusionstack.io/v1alpha1:PodTransitionRule:default:default-dev-foo", Type: "Kubernetes", Attributes: map[string]interface{}{ "apiVersion": "apps.kusionstack.io/v1alpha1", @@ -52,7 +51,7 @@ func TestOpsRuleModule_Generate(t *testing.T) { "metadata": map[string]interface{}{ "creationTimestamp": interface{}(nil), "name": "default-dev-foo", - "namespace": "foo", + "namespace": "default", }, "spec": map[string]interface{}{ "rules": []interface{}{map[string]interface{}{ @@ -87,24 +86,18 @@ func TestOpsRuleModule_Generate(t *testing.T) { Type: workload.Collaset, }, } - ops30 := map[string]interface{}{ + devConfig := map[string]interface{}{ "maxUnavailable": "30%", } - ops40 := map[string]interface{}{ + platformConfig := map[string]interface{}{ "maxUnavailable": 40, } - devConfig := jsonutil.Marshal2String(ops30) - platformConfig := jsonutil.Marshal2String(ops40) - jobWorkload := jsonutil.Marshal2String(jobWorkloadConfig) - serviceWorkload := jsonutil.Marshal2String(serviceWorkloadConfig) - res30 := jsonutil.Marshal2String(resConfig30) - res40 := jsonutil.Marshal2String(resConfig40) - response30 := &proto.GeneratorResponse{ - Resources: [][]byte{[]byte(res30)}, + response30 := &module.GeneratorResponse{ + Resources: []v1.Resource{resConfig30}, } - response40 := &proto.GeneratorResponse{ - Resources: [][]byte{[]byte(res40)}, + response40 := &module.GeneratorResponse{ + Resources: []v1.Resource{resConfig40}, } project := "default" @@ -112,39 +105,39 @@ func TestOpsRuleModule_Generate(t *testing.T) { app := "foo" type args struct { - r *proto.GeneratorRequest + r *module.GeneratorRequest } tests := []struct { name string args args - want *proto.GeneratorResponse + want *module.GeneratorResponse wantErr bool }{ { name: "test Job", args: args{ - r: &proto.GeneratorRequest{ + r: &module.GeneratorRequest{ Project: project, Stack: stack, App: app, - Workload: []byte(jobWorkload), - DevModuleConfig: []byte(devConfig), - PlatformModuleConfig: []byte(platformConfig), + Workload: jobWorkloadConfig, + DevModuleConfig: devConfig, + PlatformModuleConfig: platformConfig, RuntimeConfig: nil, }, }, - want: generators.EmptyResponse(), + want: nil, }, { name: "test CollaSet with opsRule in appConfig", args: args{ - r: &proto.GeneratorRequest{ + r: &module.GeneratorRequest{ Project: project, Stack: stack, App: app, - Workload: []byte(serviceWorkload), - DevModuleConfig: []byte(devConfig), - PlatformModuleConfig: []byte(platformConfig), + Workload: serviceWorkloadConfig, + DevModuleConfig: devConfig, + PlatformModuleConfig: platformConfig, }, }, wantErr: false, @@ -153,12 +146,12 @@ func TestOpsRuleModule_Generate(t *testing.T) { { name: "test CollaSet with opsRule in workspace", args: args{ - r: &proto.GeneratorRequest{ + r: &module.GeneratorRequest{ Project: project, Stack: stack, App: app, - Workload: []byte(serviceWorkload), - PlatformModuleConfig: []byte(platformConfig), + Workload: serviceWorkloadConfig, + PlatformModuleConfig: platformConfig, }, }, wantErr: false, @@ -168,13 +161,15 @@ func TestOpsRuleModule_Generate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &OpsRuleModule{} - got, err := o.Generate(tt.args.r) + got, err := o.Generate(context.Background(), tt.args.r) if (err != nil) != tt.wantErr { t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Generate() got = %v, want %v", got, tt.want) + out, _ := yaml.Marshal(got) + out2, _ := yaml.Marshal(tt.want) + if !reflect.DeepEqual(string(out), string(out2)) { + t.Errorf("Generate()\ngot = %v\nwant = %v", string(out), string(out2)) } }) }