runner/runnergroup_run.go (119 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. package runner import ( "context" "fmt" "time" "github.com/Azure/kperf/api/types" "github.com/Azure/kperf/helmcli" "github.com/Azure/kperf/manifests" "gopkg.in/yaml.v3" ) var ( defaultRunCmdCfg = runCmdConfig{ runnerGroupFlowcontrol: struct { priorityLevel string matchingPrecedence int }{ priorityLevel: "workload-low", matchingPrecedence: 1000, }, } ) // CreateRunnerGroupServer creates a long running server to deploy runner groups. // // TODO: // 1. create a new package to define ErrNotFound, ErrAlreadyExists, ... errors. // 2. support configurable timeout. func CreateRunnerGroupServer(ctx context.Context, kubeconfigPath string, runnerImage string, rgSpec *types.RunnerGroupSpec, runnerVerbosity int, opts ...RunCmdOpt, ) error { specInStr, err := tweakAndMarshalSpec(rgSpec) if err != nil { return err } cfg := defaultRunCmdCfg for _, opt := range opts { opt(&cfg) } appiler, err := cfg.toServerHelmValuesAppiler() if err != nil { return err } getCli, err := helmcli.NewGetCli(kubeconfigPath, runnerGroupReleaseNamespace) if err != nil { return fmt.Errorf("failed to create helm get client: %w", err) } _, err = getCli.Get(runnerGroupServerReleaseName) if err == nil { return fmt.Errorf("runner group server already exists") } ch, err := manifests.LoadChart(runnerGroupServerChartName) if err != nil { return fmt.Errorf("failed to load runner group server chart: %w", err) } releaseCli, err := helmcli.NewReleaseCli( kubeconfigPath, runnerGroupReleaseNamespace, runnerGroupServerReleaseName, ch, runnerGroupReleaseLabels, helmcli.StringPathValuesApplier( "name="+runnerGroupServerReleaseName, "image="+runnerImage, "runnerGroupSpec="+specInStr, // runnerVerbosity needs to be surrounded by quotes, so that YAML parse it as a string. fmt.Sprintf("runnerVerbosity=\"%d\"", runnerVerbosity), ), appiler, ) if err != nil { return fmt.Errorf("failed to create helm release client: %w", err) } return releaseCli.Deploy(ctx, 120*time.Second) } // tweakAndMarshalSpec updates spec's service account if not set and marshals // it into string. func tweakAndMarshalSpec(spec *types.RunnerGroupSpec) (string, error) { // NOTE: It should be aligned with ../manifests/runnergroup/server/templates/pod.yaml. if spec.ServiceAccount == nil { var sa = runnerGroupServerReleaseName spec.ServiceAccount = &sa } data, err := yaml.Marshal(spec) if err != nil { return "", fmt.Errorf("failed to marshal spec: %w", err) } return string(data), nil } type runCmdConfig struct { // serverNodeSelectors forces to schedule server to nodes with that specific labels. serverNodeSelectors map[string][]string // runnerGroupFlowcontrol applies flowcontrol settings to runners. // // NOTE: Please align with ../manifests/runnergroup/server/values.yaml // // FIXME(weifu): before v1.0.0, we should define type in ../manifests. runnerGroupFlowcontrol struct { priorityLevel string matchingPrecedence int } // TODO(weifu): merge name/image/specs into this } // RunCmdOpt is used to update default run command's setting. type RunCmdOpt func(*runCmdConfig) // WithRunCmdServerNodeSelectorsOpt updates server's node selectors. func WithRunCmdServerNodeSelectorsOpt(labels map[string][]string) RunCmdOpt { return func(cfg *runCmdConfig) { cfg.serverNodeSelectors = labels } } // WithRunCmdRunnerGroupFlowControl updates runner groups' flowcontrol. func WithRunCmdRunnerGroupFlowControl(priorityLevel string, matchingPrecedence int) RunCmdOpt { return func(cfg *runCmdConfig) { cfg.runnerGroupFlowcontrol.priorityLevel = priorityLevel cfg.runnerGroupFlowcontrol.matchingPrecedence = matchingPrecedence } } // toServerHelmValuesAppiler creates ValuesApplier. // // NOTE: It should be aligned with ../manifests/runnergroup/server/values.yaml. func (cfg *runCmdConfig) toServerHelmValuesAppiler() (helmcli.ValuesApplier, error) { values := map[string]interface{}{ "nodeSelectors": cfg.serverNodeSelectors, "flowcontrol": map[string]interface{}{ "priorityLevelConfiguration": cfg.runnerGroupFlowcontrol.priorityLevel, "matchingPrecedence": cfg.runnerGroupFlowcontrol.matchingPrecedence, }, } rawData, err := yaml.Marshal(values) if err != nil { return nil, fmt.Errorf("failed to render run command config into YAML: %w", err) } appiler, err := helmcli.YAMLValuesApplier(string(rawData)) if err != nil { return nil, fmt.Errorf("failed to prepare value appiler for run command config: %w", err) } return appiler, nil }