Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a ruleset generator to support the maxavailable deploy strategy. #494

Merged
merged 1 commit into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package trait

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"kusionstack.io/kube-api/apps/v1alpha1"
"kusionstack.io/kusion/pkg/generator/appconfiguration"
"kusionstack.io/kusion/pkg/models"
appmodule "kusionstack.io/kusion/pkg/models/appconfiguration"
"kusionstack.io/kusion/pkg/models/appconfiguration/workload"
"kusionstack.io/kusion/pkg/projectstack"
)

type opsRuleGenerator struct {
project *projectstack.Project
stack *projectstack.Stack
appName string
app *appmodule.AppConfiguration
}

func NewOpsRuleGenerator(
project *projectstack.Project,
stack *projectstack.Stack,
appName string,
app *appmodule.AppConfiguration,
) (appconfiguration.Generator, error) {
return &opsRuleGenerator{
project: project,
stack: stack,
appName: appName,
app: app,
}, nil
}

func NewOpsRuleGeneratorFunc(
project *projectstack.Project,
stack *projectstack.Stack,
appName string,
app *appmodule.AppConfiguration,
) appconfiguration.NewGeneratorFunc {
return func() (appconfiguration.Generator, error) {
return NewOpsRuleGenerator(project, stack, appName, app)
}
}

func (g *opsRuleGenerator) Generate(spec *models.Spec) error {
if g.app.Workload.Header.Type != workload.TypeService {
return nil
}

switch g.app.Workload.Service.Type {
case workload.TypeCollaset:
resource := &v1alpha1.RuleSet{
TypeMeta: metav1.TypeMeta{
APIVersion: v1alpha1.GroupVersion.String(),
Kind: "RuleSet",
},
ObjectMeta: metav1.ObjectMeta{
Name: appconfiguration.UniqueAppName(g.project.Name, g.stack.Name, g.appName),
Namespace: g.project.Name,
},
Spec: v1alpha1.RuleSetSpec{
Selector: metav1.LabelSelector{
MatchLabels: appconfiguration.UniqueAppLabels(g.project.Name, g.appName),
},
},
}
return appconfiguration.AppendToSpec(models.Kubernetes, appconfiguration.KubernetesResourceID(resource.TypeMeta, resource.ObjectMeta), spec, resource)
case workload.TypeDeploy:
// TODO: add maxUnavailable to deployment
return nil
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package trait

import (
"testing"

"github.com/stretchr/testify/require"
"kusionstack.io/kusion/pkg/models"
appmodule "kusionstack.io/kusion/pkg/models/appconfiguration"
"kusionstack.io/kusion/pkg/models/appconfiguration/trait"
"kusionstack.io/kusion/pkg/models/appconfiguration/workload"
"kusionstack.io/kusion/pkg/projectstack"
)

func Test_opsRuleGenerator_Generate(t *testing.T) {
type fields struct {
project *projectstack.Project
stack *projectstack.Stack
appName string
app *appmodule.AppConfiguration
}
type args struct {
spec *models.Spec
}
project := &projectstack.Project{
ProjectConfiguration: projectstack.ProjectConfiguration{
Name: "default",
},
}
stack := &projectstack.Stack{
StackConfiguration: projectstack.StackConfiguration{Name: "dev"},
}
appName := "foo"
tests := []struct {
name string
fields fields
args args
wantErr bool
exp *models.Spec
}{
{
name: "test Job",
fields: fields{
project: project,
stack: stack,
appName: appName,
app: &appmodule.AppConfiguration{
Workload: &workload.Workload{
Header: workload.Header{
Type: workload.TypeJob,
},
},
OpsRule: &trait.OpsRule{
MaxUnavailable: "30%",
},
},
},
args: args{
spec: &models.Spec{},
},
wantErr: false,
exp: &models.Spec{},
},
{
name: "test CollaSet",
fields: fields{
project: project,
stack: stack,
appName: appName,
app: &appmodule.AppConfiguration{
Workload: &workload.Workload{
Header: workload.Header{
Type: workload.TypeService,
},
Service: &workload.Service{
Type: workload.TypeCollaset,
},
},
OpsRule: &trait.OpsRule{
MaxUnavailable: "30%",
},
},
},
args: args{
spec: &models.Spec{},
},
wantErr: false,
exp: &models.Spec{
Resources: []models.Resource{
{
ID: "apps.kusionstack.io/v1alpha1:RuleSet:default:default-dev-foo",
Type: "Kubernetes",
Attributes: map[string]interface{}{
"apiVersion": "apps.kusionstack.io/v1alpha1",
"kind": "RuleSet",
"metadata": map[string]interface{}{
"creationTimestamp": nil,
"name": "default-dev-foo",
"namespace": "default",
},
"spec": map[string]interface{}{
"selector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app.kubernetes.io/name": "foo",
"app.kubernetes.io/part-of": "default",
},
},
},
"status": map[string]interface{}{},
},
DependsOn: nil,
Extensions: nil,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := &opsRuleGenerator{
project: tt.fields.project,
stack: tt.fields.stack,
appName: tt.fields.appName,
app: tt.fields.app,
}
err := g.Generate(tt.args.spec)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.exp, tt.args.spec)
}
})
}
}

func TestNewOpsRuleGeneratorFunc(t *testing.T) {
type args struct {
project *projectstack.Project
stack *projectstack.Stack
appName string
app *appmodule.AppConfiguration
}
tests := []struct {
name string
args args
wantErr bool
want *opsRuleGenerator
}{
{
name: "test1",
args: args{
project: nil,
stack: nil,
appName: "",
app: nil,
},
wantErr: false,
want: &opsRuleGenerator{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := NewOpsRuleGeneratorFunc(tt.args.project, tt.args.stack, tt.args.appName, tt.args.app)
g, err := f()
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.want, g)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,11 @@ func (g *workloadServiceGenerator) Generate(spec *models.Spec) error {
var resource any
typeMeta := metav1.TypeMeta{}

const (
deploy = "Deployment"
collaset = "CollaSet"
)

switch service.Type {
case deploy:
case workload.TypeDeploy:
typeMeta = metav1.TypeMeta{
APIVersion: appsv1.SchemeGroupVersion.String(),
Kind: deploy,
Kind: workload.TypeDeploy,
}
resource = &appsv1.Deployment{
TypeMeta: typeMeta,
Expand All @@ -137,10 +132,10 @@ func (g *workloadServiceGenerator) Generate(spec *models.Spec) error {
Template: podTemplateSpec,
},
}
case collaset:
case workload.TypeCollaset:
typeMeta = metav1.TypeMeta{
APIVersion: v1alpha1.GroupVersion.String(),
Kind: collaset,
Kind: workload.TypeCollaset,
}
resource = &v1alpha1.CollaSet{
TypeMeta: typeMeta,
Expand Down
8 changes: 6 additions & 2 deletions pkg/models/appconfiguration/appconfiguration.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package appconfiguration

import "kusionstack.io/kusion/pkg/models/appconfiguration/workload"
import (
"kusionstack.io/kusion/pkg/models/appconfiguration/trait"
"kusionstack.io/kusion/pkg/models/appconfiguration/workload"
)

// AppConfiguration is a developer-centric definition that describes
// how to run an Application.
Expand All @@ -26,7 +29,8 @@ import "kusionstack.io/kusion/pkg/models/appconfiguration/workload"
type AppConfiguration struct {
// Workload defines how to run your application code.
Workload *workload.Workload `json:"workload" yaml:"workload"`

// OpsRule specifies collection of rules that will be checked for Day-2 operation.
OpsRule *trait.OpsRule `json:"opsRule,omitempty" yaml:"opsRule,omitempty"`
// Labels and annotations can be used to attach arbitrary metadata
// as key-value pairs to resources.
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
Expand Down
6 changes: 6 additions & 0 deletions pkg/models/appconfiguration/appconfiguration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"

"kusionstack.io/kusion/pkg/models/appconfiguration/trait"
"kusionstack.io/kusion/pkg/models/appconfiguration/workload"
"kusionstack.io/kusion/pkg/models/appconfiguration/workload/container"
)
Expand All @@ -22,6 +23,8 @@ var (
- echo hello
replicas: 2
schedule: 0 * * * *
opsRule:
maxUnavailable: 30%
`
appStruct = AppConfiguration{
Workload: &workload.Workload{
Expand All @@ -41,6 +44,9 @@ var (
Schedule: "0 * * * *",
},
},
OpsRule: &trait.OpsRule{
MaxUnavailable: "30%",
},
}
)

Expand Down
5 changes: 5 additions & 0 deletions pkg/models/appconfiguration/trait/ops_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package trait

type OpsRule struct {
MaxUnavailable string `json:"maxUnavailable,omitempty" yaml:"maxUnavailable,omitempty"`
}
5 changes: 5 additions & 0 deletions pkg/models/appconfiguration/workload/service.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package workload

const (
TypeDeploy = "Deployment"
TypeCollaset = "CollaSet"
)

// Service is a kind of workload profile that describes how to run
// your application code. This is typically used for long-running web
// applications that should "never" go down, and handle short-lived
Expand Down
Loading