package apply // Author: Weisen Pan // Date: 2023-10-24 import ( "encoding/json" "fmt" "io/ioutil" "os" "sort" "strconv" survey "github.com/AlecAivazis/survey/v2" "github.com/olekukonko/tablewriter" "github.com/pquerna/ffjson/ffjson" log "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/knets_pkg/api/resource" resourcehelper "k8s.io/kubectl/knets_pkg/util/resource" "sigs.k8s.io/yaml" localcache "github.com/alibaba/open-local/knets_pkg/scheduler/algorithm/cache" "github.com/hkust-adsl/kubernetes-scheduler-simulator/knets_pkg/api/v1alpha1" "github.com/hkust-adsl/kubernetes-scheduler-simulator/knets_pkg/chart" "github.com/hkust-adsl/kubernetes-scheduler-simulator/knets_pkg/simulator" simontype "github.com/hkust-adsl/kubernetes-scheduler-simulator/knets_pkg/type" gpushareutils "github.com/hkust-adsl/kubernetes-scheduler-simulator/knets_pkg/type/open-gpu-share/utils" "github.com/hkust-adsl/kubernetes-scheduler-simulator/knets_pkg/utils" ) type Options struct { SimonConfig string DefaultSchedulerConfigFile string UseGreed bool Interactive bool ExtendedResources []string } type Applier struct { cluster v1alpha1.Cluster appList []v1alpha1.AppInfo schedulerConfig string useGreed bool interactive bool extendedResources []string customConfig v1alpha1.CustomConfig } type Interface interface { Run() error } // NewApplier returns a default applier that has passed the validity test func NewApplier(opts Options) Interface { simonCR := &v1alpha1.Simon{} configFile, err := ioutil.ReadFile(opts.SimonConfig) if err != nil { log.Fatalf("failed to read config file(%s): %v", opts.SimonConfig, err) } configJSON, err := yaml.YAMLToJSON(configFile) if err != nil { log.Fatalf("failed to unmarshal config file(%s) to json: %v", opts.SimonConfig, err) } if err := json.Unmarshal(configJSON, simonCR); err != nil { log.Fatalf("failed to unmarshal config json to object: %v", err) } applier := &Applier{ cluster: simonCR.Spec.Cluster, appList: simonCR.Spec.AppList, customConfig: simonCR.Spec.CustomConfig, schedulerConfig: opts.DefaultSchedulerConfigFile, useGreed: opts.UseGreed, interactive: opts.Interactive, extendedResources: opts.ExtendedResources, } if err := validate(applier); err != nil { fmt.Printf("%v", err) os.Exit(1) } return applier } func (applier *Applier) Run() (err error) { var ( resourceMap map[string]simulator.ResourceTypes resourceList []string content []string ) resourceMap = make(map[string]simulator.ResourceTypes) // convert the application files into the kubernetes objects and generate a ResourceTypes struct, then make a resource list var appResource simulator.ResourceTypes for _, app := range applier.appList { // process separately chart and other file if app.Chart { // parse and render chart as a yaml array if content, err = chart.ProcessChart(app.Name, app.Path); err != nil { return err } } else { if content, err = utils.GetYamlContentFromDirectory(app.Path); err != nil { return err } } if appResource, err = simulator.GetObjectFromYamlContent(content); err != nil { return err } resourceMap[app.Name] = appResource resourceList = append(resourceList, app.Name) } // convert the cluster files into the kubernetes objects and generate a ResourceTypes struct // cluster resource generated by two types of cluster, custom cluster and real cluster var clusterResource simulator.ResourceTypes if applier.cluster.KubeConfig != "" { // generate kube-client kubeclient, err := utils.CreateKubeClient(applier.cluster.KubeConfig) if err != nil { return err } if clusterResource, err = simulator.CreateClusterResourceFromClient(kubeclient); err != nil { return err } } else { if clusterResource, err = simulator.CreateClusterResourceFromClusterConfig(applier.cluster.CustomCluster); err != nil { return err } } // confirm the list of applications that needed to be deployed in interactive mode var selectedAppNameList []string var selectedResourceList []simulator.AppResource if applier.interactive { var multiQs = []*survey.Question{ { Name: "APPs", Prompt: &survey.MultiSelect{ Message: "Confirm your apps :", Options: resourceList, }, }, } err = survey.Ask(multiQs, &selectedAppNameList) if err != nil { log.Fatalf("%v", err) } } else { selectedAppNameList = resourceList } for _, name := range selectedAppNameList { selectedResourceList = append(selectedResourceList, simulator.AppResource{ Name: name, Resource: resourceMap[name], }) } // Run the simulator success := false var result *simontype.SimulateResult result, err = simulator.Simulate(clusterResource, selectedResourceList, simulator.WithSchedulerConfig(applier.schedulerConfig), simulator.WithKubeConfig(applier.cluster.KubeConfig), simulator.WithCustomConfig(applier.customConfig)) if err != nil { return err } if len(result.UnscheduledPods) == 0 { if ok, reason, err := satisfyResourceSetting(result.NodeStatus); err != nil { return err } else if !ok { fmt.Printf(utils.ColorRed+"%s"+utils.ColorReset, reason) } else { success = true } } else { fmt.Printf(utils.ColorRed+"there are %d unscheduled pods\n"+utils.ColorReset, len(result.UnscheduledPods)) log.Infof("there are %d unscheduled pods\n", len(result.UnscheduledPods)) allDaemonSets := clusterResource.DaemonSets for _, app := range selectedResourceList { allDaemonSets = append(allDaemonSets, app.Resource.DaemonSets...) } for _, unScheduledPod := range result.UnscheduledPods { log.Debugf("failed to schedule pod %s/%s: %s", unScheduledPod.Pod.Namespace, unScheduledPod.Pod.Name, unScheduledPod.Reason) } } if success { fmt.Printf(utils.ColorGreen + "Success!\n" + utils.ColorReset) } else { fmt.Printf(utils.ColorRed + "Failed!\n" + utils.ColorReset) } return nil } func validate(applier *Applier) error { fmt.Printf("cluster.KubeConfig=%s\n", applier.cluster.KubeConfig) fmt.Printf("cluster.CustomCluster=%s\n", applier.cluster.CustomCluster) if len(applier.cluster.KubeConfig) == 0 && len(applier.cluster.CustomCluster) == 0 || len(applier.cluster.KubeConfig) != 0 && len(applier.cluster.CustomCluster) != 0 { return fmt.Errorf("only one of values of both kubeConfig and customConfig must exist ") } if len(applier.cluster.KubeConfig) != 0 { if _, err