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: update workload generator support probe&lifecycle #495

Merged
merged 1 commit into from
Aug 28, 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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ require (
github.com/variantdev/vals v0.21.0
github.com/zclconf/go-cty v1.12.1
go.uber.org/zap v1.24.0
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ package workload

import (
"fmt"
"net/url"
"strings"

corev1 "k8s.io/api/core/v1"
"golang.org/x/exp/maps"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/intstr"

"kusionstack.io/kusion/pkg/generator/appconfiguration"
"kusionstack.io/kusion/pkg/models"
"kusionstack.io/kusion/pkg/models/appconfiguration/workload"
"kusionstack.io/kusion/pkg/models/appconfiguration/workload/container"
"kusionstack.io/kusion/pkg/projectstack"
"kusionstack.io/kusion/pkg/util/net"
)

type workloadGenerator struct {
Expand Down Expand Up @@ -71,29 +77,229 @@ func (g *workloadGenerator) Generate(spec *models.Spec) error {
return nil
}

func toOrderedContainers(appContainers map[string]container.Container) ([]corev1.Container, error) {
func toOrderedContainers(appContainers map[string]container.Container) ([]v1.Container, error) {
// Create a slice of containers based on the app's
// containers.
var containers []corev1.Container
var containers []v1.Container
if err := appconfiguration.ForeachOrdered(appContainers, func(containerName string, c container.Container) error {
// Create a slice of env vars based on the container's env vars.
var envs []corev1.EnvVar
var envs []v1.EnvVar
for k, v := range c.Env {
envs = append(envs, *MagicEnvVar(k, v))
}
resourceRequirements, err := handleResourceRequirementsV1(c.Resources)
if err != nil {
return err
}

// Create a container object and append it to the containers slice.
containers = append(containers, corev1.Container{
ctn := v1.Container{
Name: containerName,
Image: c.Image,
Command: c.Command,
Args: c.Args,
WorkingDir: c.WorkingDir,
Env: envs,
})
Resources: resourceRequirements,
}
err = updateContainer(&c, &ctn)
if err != nil {
return err
}
// Create a container object and append it to the containers slice.
containers = append(containers, ctn)
return nil
}); err != nil {
return nil, err
}
return containers, nil
}

// updateContainer updates v1.Container with passed parameters.
func updateContainer(in *container.Container, out *v1.Container) error {
if in.ReadinessProbe != nil {
readinessProbe, err := convertKusionProbeToV1Probe(in.ReadinessProbe)
if err != nil {
return err
}
out.ReadinessProbe = readinessProbe
}

if in.LivenessProbe != nil {
livenessProbe, err := convertKusionProbeToV1Probe(in.LivenessProbe)
if err != nil {
return err
}
out.LivenessProbe = livenessProbe
}

if in.StartupProbe != nil {
startupProbe, err := convertKusionProbeToV1Probe(in.StartupProbe)
if err != nil {
return err
}
out.StartupProbe = startupProbe
}

if in.Lifecycle != nil {
lifecycle, err := convertKusionLifecycleToV1Lifecycle(in.Lifecycle)
if err != nil {
return err
}
out.Lifecycle = lifecycle
}

return nil
}

// handleResourceRequirementsV1 parses the resources parameter if specified and
// returns ResourceRequirements.
func handleResourceRequirementsV1(resources map[string]string) (v1.ResourceRequirements, error) {
result := v1.ResourceRequirements{}
if resources == nil {
return result, nil
}
for key, value := range resources {
resourceName := v1.ResourceName(key)
requests, limits, err := populateResourceLists(resourceName, value)
if err != nil {
return result, err
}
if requests != nil && result.Requests == nil {
result.Requests = make(v1.ResourceList)
}
maps.Copy(result.Requests, requests)
if limits != nil && result.Limits == nil {
result.Limits = make(v1.ResourceList)
}
maps.Copy(result.Limits, limits)
}
return result, nil
}

// populateResourceLists takes strings of form <resourceName>=[<minValue>-]<maxValue> and
// returns request&limit ResourceList.
func populateResourceLists(name v1.ResourceName, spec string) (v1.ResourceList, v1.ResourceList, error) {
requests := v1.ResourceList{}
limits := v1.ResourceList{}

parts := strings.Split(spec, "-")
if len(parts) == 1 {
resourceQuantity, err := resource.ParseQuantity(parts[0])
if err != nil {
return nil, nil, err
}
limits[name] = resourceQuantity
} else if len(parts) == 2 {
resourceQuantity, err := resource.ParseQuantity(parts[0])
if err != nil {
return nil, nil, err
}
requests[name] = resourceQuantity
resourceQuantity, err = resource.ParseQuantity(parts[1])
if err != nil {
return nil, nil, err
}
limits[name] = resourceQuantity
}

return requests, limits, nil
}

// convertKusionProbeToV1Probe converts Kusion Probe to Kubernetes Probe types.
func convertKusionProbeToV1Probe(p *container.Probe) (*v1.Probe, error) {
result := &v1.Probe{
InitialDelaySeconds: p.InitialDelaySeconds,
TimeoutSeconds: p.TimeoutSeconds,
PeriodSeconds: p.PeriodSeconds,
SuccessThreshold: p.SuccessThreshold,
FailureThreshold: p.FailureThreshold,
}
probeHandler := p.ProbeHandler
switch probeHandler.Type {
case "Http":
action, err := httpGetAction(probeHandler.HTTPGetAction.URL, probeHandler.Headers)
if err != nil {
return nil, err
}
result.HTTPGet = action
case "Exec":
result.Exec = &v1.ExecAction{Command: probeHandler.Command}
case "Tcp":
action, err := tcpSocketAction(probeHandler.TCPSocketAction.URL)
if err != nil {
return nil, err
}
result.TCPSocket = action
}
return result, nil
}

// convertKusionLifecycleToV1Lifecycle converts Kusion Lifecycle to Kubernetes Lifecycle types.
func convertKusionLifecycleToV1Lifecycle(l *container.Lifecycle) (*v1.Lifecycle, error) {
result := &v1.Lifecycle{}
if l.PreStop != nil {
preStop, err := lifecycleHandler(l.PreStop)
if err != nil {
return nil, err
}
result.PreStop = preStop
}
if l.PostStart != nil {
postStart, err := lifecycleHandler(l.PostStart)
if err != nil {
return nil, err
}
result.PostStart = postStart
}
return result, nil
}

func lifecycleHandler(in *container.LifecycleHandler) (*v1.LifecycleHandler, error) {
result := &v1.LifecycleHandler{}
switch in.Type {
case "Http":
action, err := httpGetAction(in.HTTPGetAction.URL, in.Headers)
if err != nil {
return nil, err
}
result.HTTPGet = action
case "Exec":
result.Exec = &v1.ExecAction{Command: in.Command}
}
return result, nil
}

func httpGetAction(urlstr string, headers map[string]string) (*v1.HTTPGetAction, error) {
u, err := url.Parse(urlstr)
if err != nil {
return nil, err
}

httpHeaders := make([]v1.HTTPHeader, 0, len(headers))
for k, v := range headers {
httpHeaders = append(httpHeaders, v1.HTTPHeader{
Name: k,
Value: v,
})
}

return &v1.HTTPGetAction{
Path: u.Path,
Port: intstr.FromString(u.Port()),
Host: u.Hostname(),
Scheme: v1.URIScheme(strings.ToUpper(u.Scheme)),
HTTPHeaders: httpHeaders,
}, nil
}

func tcpSocketAction(urlstr string) (*v1.TCPSocketAction, error) {
host, port, err := net.ParseHostPort(urlstr)
if err != nil {
return nil, err
}

return &v1.TCPSocketAction{
Port: intstr.FromString(port),
Host: host,
}, nil
}
Loading
Loading