Signed-off-by: llhuii <liulinghui@huawei.com>tags/v0.3.1
| @@ -1,15 +1,26 @@ | |||
| /* | |||
| Copyright 2021 The KubeEdge Authors. | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| */ | |||
| package dataset | |||
| import ( | |||
| "context" | |||
| "encoding/json" | |||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |||
| sednav1 "github.com/kubeedge/sedna/pkg/apis/sedna/v1alpha1" | |||
| sednaclientset "github.com/kubeedge/sedna/pkg/client/clientset/versioned/typed/sedna/v1alpha1" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/config" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/runtime" | |||
| sednaclientset "github.com/kubeedge/sedna/pkg/client/clientset/versioned/typed/sedna/v1alpha1" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/runtime" | |||
| ) | |||
| const ( | |||
| @@ -27,37 +38,6 @@ type Controller struct { | |||
| cfg *config.ControllerConfig | |||
| } | |||
| // updateFromEdge syncs update from edge | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) error { | |||
| status := sednav1.DatasetStatus{} | |||
| err := json.Unmarshal(content, &status) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return c.updateStatus(name, namespace, status) | |||
| } | |||
| // updateStatus updates the dataset status | |||
| func (c *Controller) updateStatus(name, namespace string, status sednav1.DatasetStatus) error { | |||
| client := c.client.Datasets(namespace) | |||
| if status.UpdateTime == nil { | |||
| now := metav1.Now() | |||
| status.UpdateTime = &now | |||
| } | |||
| return runtime.RetryUpdateStatus(name, namespace, func() error { | |||
| dataset, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| dataset.Status = status | |||
| _, err = client.UpdateStatus(context.TODO(), dataset, metav1.UpdateOptions{}) | |||
| return err | |||
| }) | |||
| } | |||
| func (c *Controller) Run(stopCh <-chan struct{}) { | |||
| // noop now | |||
| } | |||
| @@ -68,8 +48,7 @@ func New(cc *runtime.ControllerContext) (runtime.FeatureControllerI, error) { | |||
| client: cc.SednaClient.SednaV1alpha1(), | |||
| } | |||
| // only upstream | |||
| cc.UpstreamController.Add(KindName, c.updateFromEdge) | |||
| c.addUpstreamHandler(cc) | |||
| return c, nil | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| /* | |||
| Copyright 2021 The KubeEdge Authors. | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| */ | |||
| package dataset | |||
| @@ -0,0 +1,62 @@ | |||
| /* | |||
| Copyright 2021 The KubeEdge Authors. | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| */ | |||
| package dataset | |||
| import ( | |||
| "context" | |||
| "encoding/json" | |||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |||
| sednav1 "github.com/kubeedge/sedna/pkg/apis/sedna/v1alpha1" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/runtime" | |||
| ) | |||
| // updateFromEdge syncs update from edge | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) error { | |||
| status := sednav1.DatasetStatus{} | |||
| err := json.Unmarshal(content, &status) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return c.updateStatus(name, namespace, status) | |||
| } | |||
| // updateStatus updates the dataset status | |||
| func (c *Controller) updateStatus(name, namespace string, status sednav1.DatasetStatus) error { | |||
| client := c.client.Datasets(namespace) | |||
| if status.UpdateTime == nil { | |||
| now := metav1.Now() | |||
| status.UpdateTime = &now | |||
| } | |||
| return runtime.RetryUpdateStatus(name, namespace, func() error { | |||
| dataset, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| dataset.Status = status | |||
| _, err = client.UpdateStatus(context.TODO(), dataset, metav1.UpdateOptions{}) | |||
| return err | |||
| }) | |||
| } | |||
| func (c *Controller) addUpstreamHandler(cc *runtime.ControllerContext) error { | |||
| return cc.UpstreamController.Add(KindName, c.updateFromEdge) | |||
| } | |||
| @@ -18,7 +18,6 @@ package federatedlearning | |||
| import ( | |||
| "context" | |||
| "encoding/json" | |||
| "fmt" | |||
| "strconv" | |||
| "time" | |||
| @@ -527,98 +526,6 @@ func (c *Controller) createPod(job *sednav1.FederatedLearningJob) (active int32, | |||
| return | |||
| } | |||
| func (c *Controller) updateModelMetrics(jobName, namespace string, metrics []sednav1.Metric) error { | |||
| var err error | |||
| job, err := c.client.FederatedLearningJobs(namespace).Get(context.TODO(), jobName, metav1.GetOptions{}) | |||
| if err != nil { | |||
| // federated crd not found | |||
| return err | |||
| } | |||
| modelName := job.Spec.AggregationWorker.Model.Name | |||
| client := c.client.Models(namespace) | |||
| return runtime.RetryUpdateStatus(modelName, namespace, (func() error { | |||
| model, err := client.Get(context.TODO(), modelName, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| now := metav1.Now() | |||
| model.Status.UpdateTime = &now | |||
| model.Status.Metrics = metrics | |||
| _, err = client.UpdateStatus(context.TODO(), model, metav1.UpdateOptions{}) | |||
| return err | |||
| })) | |||
| } | |||
| func (c *Controller) appendStatusCondition(name, namespace string, cond sednav1.FLJobCondition) error { | |||
| client := c.client.FederatedLearningJobs(namespace) | |||
| return runtime.RetryUpdateStatus(name, namespace, (func() error { | |||
| job, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| job.Status.Conditions = append(job.Status.Conditions, cond) | |||
| _, err = client.UpdateStatus(context.TODO(), job, metav1.UpdateOptions{}) | |||
| return err | |||
| })) | |||
| } | |||
| // updateFromEdge updates the federated job's status | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) (err error) { | |||
| // JobInfo defines the job information | |||
| type JobInfo struct { | |||
| // Current training round | |||
| CurrentRound int `json:"currentRound"` | |||
| UpdateTime string `json:"updateTime"` | |||
| } | |||
| // Output defines job output information | |||
| type Output struct { | |||
| Models []runtime.Model `json:"models"` | |||
| JobInfo *JobInfo `json:"ownerInfo"` | |||
| } | |||
| var status struct { | |||
| Phase string `json:"phase"` | |||
| Status string `json:"status"` | |||
| Output *Output `json:"output"` | |||
| } | |||
| err = json.Unmarshal(content, &status) | |||
| if err != nil { | |||
| return | |||
| } | |||
| output := status.Output | |||
| if output != nil { | |||
| // Update the model's metrics | |||
| if len(output.Models) > 0 { | |||
| // only one model | |||
| model := output.Models[0] | |||
| metrics := runtime.ConvertMapToMetrics(model.Metrics) | |||
| if len(metrics) > 0 { | |||
| c.updateModelMetrics(name, namespace, metrics) | |||
| } | |||
| } | |||
| jobInfo := output.JobInfo | |||
| // update job info if having any info | |||
| if jobInfo != nil && jobInfo.CurrentRound > 0 { | |||
| // Find a good place to save the progress info | |||
| // TODO: more meaningful reason/message | |||
| reason := "DoTraining" | |||
| message := fmt.Sprintf("Round %v reaches at %s", jobInfo.CurrentRound, jobInfo.UpdateTime) | |||
| cond := NewFLJobCondition(sednav1.FLJobCondTraining, reason, message) | |||
| c.appendStatusCondition(name, namespace, cond) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // New creates a new federated learning job controller that keeps the relevant pods | |||
| // in sync with their corresponding FederatedLearningJob objects. | |||
| func New(cc *runtime.ControllerContext) (runtime.FeatureControllerI, error) { | |||
| @@ -662,7 +569,7 @@ func New(cc *runtime.ControllerContext) (runtime.FeatureControllerI, error) { | |||
| fc.podStore = podInformer.Lister() | |||
| fc.podStoreSynced = podInformer.Informer().HasSynced | |||
| cc.UpstreamController.Add(KindName, fc.updateFromEdge) | |||
| fc.addUpstreamHandler(cc) | |||
| return fc, nil | |||
| } | |||
| @@ -0,0 +1,123 @@ | |||
| /* | |||
| Copyright 2021 The KubeEdge Authors. | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| */ | |||
| package federatedlearning | |||
| import ( | |||
| "context" | |||
| "encoding/json" | |||
| "fmt" | |||
| sednav1 "github.com/kubeedge/sedna/pkg/apis/sedna/v1alpha1" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/runtime" | |||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |||
| ) | |||
| func (c *Controller) updateModelMetrics(jobName, namespace string, metrics []sednav1.Metric) error { | |||
| var err error | |||
| job, err := c.client.FederatedLearningJobs(namespace).Get(context.TODO(), jobName, metav1.GetOptions{}) | |||
| if err != nil { | |||
| // federated crd not found | |||
| return err | |||
| } | |||
| modelName := job.Spec.AggregationWorker.Model.Name | |||
| client := c.client.Models(namespace) | |||
| return runtime.RetryUpdateStatus(modelName, namespace, (func() error { | |||
| model, err := client.Get(context.TODO(), modelName, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| now := metav1.Now() | |||
| model.Status.UpdateTime = &now | |||
| model.Status.Metrics = metrics | |||
| _, err = client.UpdateStatus(context.TODO(), model, metav1.UpdateOptions{}) | |||
| return err | |||
| })) | |||
| } | |||
| func (c *Controller) appendStatusCondition(name, namespace string, cond sednav1.FLJobCondition) error { | |||
| client := c.client.FederatedLearningJobs(namespace) | |||
| return runtime.RetryUpdateStatus(name, namespace, (func() error { | |||
| job, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| job.Status.Conditions = append(job.Status.Conditions, cond) | |||
| _, err = client.UpdateStatus(context.TODO(), job, metav1.UpdateOptions{}) | |||
| return err | |||
| })) | |||
| } | |||
| // updateFromEdge updates the federated job's status | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) (err error) { | |||
| // JobInfo defines the job information | |||
| type JobInfo struct { | |||
| // Current training round | |||
| CurrentRound int `json:"currentRound"` | |||
| UpdateTime string `json:"updateTime"` | |||
| } | |||
| // Output defines job output information | |||
| type Output struct { | |||
| Models []runtime.Model `json:"models"` | |||
| JobInfo *JobInfo `json:"ownerInfo"` | |||
| } | |||
| var status struct { | |||
| Phase string `json:"phase"` | |||
| Status string `json:"status"` | |||
| Output *Output `json:"output"` | |||
| } | |||
| err = json.Unmarshal(content, &status) | |||
| if err != nil { | |||
| return | |||
| } | |||
| output := status.Output | |||
| if output != nil { | |||
| // Update the model's metrics | |||
| if len(output.Models) > 0 { | |||
| // only one model | |||
| model := output.Models[0] | |||
| metrics := runtime.ConvertMapToMetrics(model.Metrics) | |||
| if len(metrics) > 0 { | |||
| c.updateModelMetrics(name, namespace, metrics) | |||
| } | |||
| } | |||
| jobInfo := output.JobInfo | |||
| // update job info if having any info | |||
| if jobInfo != nil && jobInfo.CurrentRound > 0 { | |||
| // Find a good place to save the progress info | |||
| // TODO: more meaningful reason/message | |||
| reason := "DoTraining" | |||
| message := fmt.Sprintf("Round %v reaches at %s", jobInfo.CurrentRound, jobInfo.UpdateTime) | |||
| cond := NewFLJobCondition(sednav1.FLJobCondTraining, reason, message) | |||
| c.appendStatusCondition(name, namespace, cond) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func (c *Controller) addUpstreamHandler(cc *runtime.ControllerContext) error { | |||
| return cc.UpstreamController.Add(KindName, c.updateFromEdge) | |||
| } | |||
| @@ -599,7 +599,7 @@ func (c *Controller) createPod(job *sednav1.IncrementalLearningJob, podtype sedn | |||
| // get all url for train and eval from data in condition | |||
| condDataStr := job.Status.Conditions[len(job.Status.Conditions)-1].Data | |||
| klog.V(2).Infof("incrementallearning job %v/%v data condition:%s", job.Namespace, job.Name, condDataStr) | |||
| var cond runtime.IncrementalCondData | |||
| var cond IncrementalCondData | |||
| (&cond).Unmarshal([]byte(condDataStr)) | |||
| if cond.Input == nil { | |||
| return fmt.Errorf("empty input from condData") | |||
| @@ -812,79 +812,6 @@ func (c *Controller) createInferPod(job *sednav1.IncrementalLearningJob) error { | |||
| return err | |||
| } | |||
| func (c *Controller) appendStatusCondition(name, namespace string, cond sednav1.ILJobCondition) error { | |||
| client := c.client.IncrementalLearningJobs(namespace) | |||
| return runtime.RetryUpdateStatus(name, namespace, (func() error { | |||
| job, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| job.Status.Conditions = append(job.Status.Conditions, cond) | |||
| _, err = client.UpdateStatus(context.TODO(), job, metav1.UpdateOptions{}) | |||
| return err | |||
| })) | |||
| } | |||
| // updateFromEdge syncs the edge updates to k8s | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) error { | |||
| var jobStatus struct { | |||
| Phase string `json:"phase"` | |||
| Status string `json:"status"` | |||
| } | |||
| err := json.Unmarshal(content, &jobStatus) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Get the condition data. | |||
| // Here unmarshal and marshal immediately to skip the unnecessary fields | |||
| var condData runtime.IncrementalCondData | |||
| err = json.Unmarshal(content, &condData) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| condDataBytes, _ := json.Marshal(&condData) | |||
| cond := sednav1.ILJobCondition{ | |||
| Status: v1.ConditionTrue, | |||
| LastHeartbeatTime: metav1.Now(), | |||
| LastTransitionTime: metav1.Now(), | |||
| Data: string(condDataBytes), | |||
| Message: "reported by lc", | |||
| } | |||
| switch strings.ToLower(jobStatus.Phase) { | |||
| case "train": | |||
| cond.Stage = sednav1.ILJobTrain | |||
| case "eval": | |||
| cond.Stage = sednav1.ILJobEval | |||
| case "deploy": | |||
| cond.Stage = sednav1.ILJobDeploy | |||
| default: | |||
| return fmt.Errorf("invalid condition stage: %v", jobStatus.Phase) | |||
| } | |||
| switch strings.ToLower(jobStatus.Status) { | |||
| case "ready": | |||
| cond.Type = sednav1.ILJobStageCondReady | |||
| case "completed": | |||
| cond.Type = sednav1.ILJobStageCondCompleted | |||
| case "failed": | |||
| cond.Type = sednav1.ILJobStageCondFailed | |||
| case "waiting": | |||
| cond.Type = sednav1.ILJobStageCondWaiting | |||
| default: | |||
| return fmt.Errorf("invalid condition type: %v", jobStatus.Status) | |||
| } | |||
| err = c.appendStatusCondition(name, namespace, cond) | |||
| if err != nil { | |||
| return fmt.Errorf("failed to append condition, err:%+w", err) | |||
| } | |||
| return nil | |||
| } | |||
| // New creates a new IncrementalJob controller that keeps the relevant pods | |||
| // in sync with their corresponding IncrementalJob objects. | |||
| func New(cc *runtime.ControllerContext) (runtime.FeatureControllerI, error) { | |||
| @@ -926,7 +853,7 @@ func New(cc *runtime.ControllerContext) (runtime.FeatureControllerI, error) { | |||
| jc.podStore = podInformer.Lister() | |||
| jc.podStoreSynced = podInformer.Informer().HasSynced | |||
| cc.UpstreamController.Add(KindName, jc.updateFromEdge) | |||
| jc.addUpstreamHandler(cc) | |||
| return jc, nil | |||
| } | |||
| @@ -0,0 +1,162 @@ | |||
| /* | |||
| Copyright 2021 The KubeEdge Authors. | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| */ | |||
| package incrementallearning | |||
| import ( | |||
| "context" | |||
| "encoding/json" | |||
| "fmt" | |||
| "strings" | |||
| sednav1 "github.com/kubeedge/sedna/pkg/apis/sedna/v1alpha1" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/runtime" | |||
| v1 "k8s.io/api/core/v1" | |||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |||
| ) | |||
| type Model = runtime.Model | |||
| // the data of this condition including the input/output to do the next step | |||
| type IncrementalCondData struct { | |||
| Input *struct { | |||
| // Only one model cases | |||
| Model *Model `json:"model,omitempty"` | |||
| Models []Model `json:"models,omitempty"` | |||
| DataURL string `json:"dataURL,omitempty"` | |||
| // the data samples reference will be stored into this URL. | |||
| // The content of this url would be: | |||
| // # the first uncomment line means the directory | |||
| // s3://dataset/ | |||
| // mnist/0.jpg | |||
| // mnist/1.jpg | |||
| DataIndexURL string `json:"dataIndexURL,omitempty"` | |||
| OutputDir string `json:"outputDir,omitempty"` | |||
| } `json:"input,omitempty"` | |||
| Output *struct { | |||
| Model *Model `json:"model,omitempty"` | |||
| Models []Model `json:"models,omitempty"` | |||
| } `json:"output,omitempty"` | |||
| } | |||
| func (cd *IncrementalCondData) joinModelURLs(model *Model, models []Model) []string { | |||
| var modelURLs []string | |||
| if model != nil { | |||
| modelURLs = append(modelURLs, model.GetURL()) | |||
| } else { | |||
| for _, m := range models { | |||
| modelURLs = append(modelURLs, m.GetURL()) | |||
| } | |||
| } | |||
| return modelURLs | |||
| } | |||
| func (cd *IncrementalCondData) GetInputModelURLs() []string { | |||
| return cd.joinModelURLs(cd.Input.Model, cd.Input.Models) | |||
| } | |||
| func (cd *IncrementalCondData) GetOutputModelURLs() []string { | |||
| return cd.joinModelURLs(cd.Output.Model, cd.Output.Models) | |||
| } | |||
| func (cd *IncrementalCondData) Unmarshal(data []byte) error { | |||
| return json.Unmarshal(data, cd) | |||
| } | |||
| func (cd IncrementalCondData) Marshal() ([]byte, error) { | |||
| return json.Marshal(cd) | |||
| } | |||
| func (c *Controller) appendStatusCondition(name, namespace string, cond sednav1.ILJobCondition) error { | |||
| client := c.client.IncrementalLearningJobs(namespace) | |||
| return runtime.RetryUpdateStatus(name, namespace, (func() error { | |||
| job, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| job.Status.Conditions = append(job.Status.Conditions, cond) | |||
| _, err = client.UpdateStatus(context.TODO(), job, metav1.UpdateOptions{}) | |||
| return err | |||
| })) | |||
| } | |||
| // updateFromEdge syncs the edge updates to k8s | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) error { | |||
| var jobStatus struct { | |||
| Phase string `json:"phase"` | |||
| Status string `json:"status"` | |||
| } | |||
| err := json.Unmarshal(content, &jobStatus) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Get the condition data. | |||
| // Here unmarshal and marshal immediately to skip the unnecessary fields | |||
| var condData IncrementalCondData | |||
| err = json.Unmarshal(content, &condData) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| condDataBytes, _ := json.Marshal(&condData) | |||
| cond := sednav1.ILJobCondition{ | |||
| Status: v1.ConditionTrue, | |||
| LastHeartbeatTime: metav1.Now(), | |||
| LastTransitionTime: metav1.Now(), | |||
| Data: string(condDataBytes), | |||
| Message: "reported by lc", | |||
| } | |||
| switch strings.ToLower(jobStatus.Phase) { | |||
| case "train": | |||
| cond.Stage = sednav1.ILJobTrain | |||
| case "eval": | |||
| cond.Stage = sednav1.ILJobEval | |||
| case "deploy": | |||
| cond.Stage = sednav1.ILJobDeploy | |||
| default: | |||
| return fmt.Errorf("invalid condition stage: %v", jobStatus.Phase) | |||
| } | |||
| switch strings.ToLower(jobStatus.Status) { | |||
| case "ready": | |||
| cond.Type = sednav1.ILJobStageCondReady | |||
| case "completed": | |||
| cond.Type = sednav1.ILJobStageCondCompleted | |||
| case "failed": | |||
| cond.Type = sednav1.ILJobStageCondFailed | |||
| case "waiting": | |||
| cond.Type = sednav1.ILJobStageCondWaiting | |||
| default: | |||
| return fmt.Errorf("invalid condition type: %v", jobStatus.Status) | |||
| } | |||
| err = c.appendStatusCondition(name, namespace, cond) | |||
| if err != nil { | |||
| return fmt.Errorf("failed to append condition, err:%+w", err) | |||
| } | |||
| return nil | |||
| } | |||
| func (c *Controller) addUpstreamHandler(cc *runtime.ControllerContext) error { | |||
| return cc.UpstreamController.Add(KindName, c.updateFromEdge) | |||
| } | |||
| @@ -533,66 +533,6 @@ func (c *Controller) createEdgeWorker(service *sednav1.JointInferenceService, bi | |||
| return err | |||
| } | |||
| func (c *Controller) updateMetrics(name, namespace string, metrics []sednav1.Metric) error { | |||
| client := c.client.JointInferenceServices(namespace) | |||
| return runtime.RetryUpdateStatus(name, namespace, func() error { | |||
| joint, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| joint.Status.Metrics = metrics | |||
| _, err = client.UpdateStatus(context.TODO(), joint, metav1.UpdateOptions{}) | |||
| return err | |||
| }) | |||
| } | |||
| // updateFromEdge syncs the edge updates to k8s | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) error { | |||
| // Output defines owner output information | |||
| type Output struct { | |||
| ServiceInfo map[string]interface{} `json:"ownerInfo"` | |||
| } | |||
| var status struct { | |||
| // Phase always should be "inference" | |||
| Phase string `json:"phase"` | |||
| Status string `json:"status"` | |||
| Output *Output `json:"output"` | |||
| } | |||
| err := json.Unmarshal(content, &status) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // TODO: propagate status.Status to k8s | |||
| output := status.Output | |||
| if output == nil || output.ServiceInfo == nil { | |||
| // no output info | |||
| klog.Warningf("empty status info for joint inference service %s/%s", namespace, name) | |||
| return nil | |||
| } | |||
| info := output.ServiceInfo | |||
| for _, ignoreTimeKey := range []string{ | |||
| "startTime", | |||
| "updateTime", | |||
| } { | |||
| delete(info, ignoreTimeKey) | |||
| } | |||
| metrics := runtime.ConvertMapToMetrics(info) | |||
| err = c.updateMetrics(name, namespace, metrics) | |||
| if err != nil { | |||
| return fmt.Errorf("failed to update metrics, err:%+w", err) | |||
| } | |||
| return nil | |||
| } | |||
| // New creates a new JointInferenceService controller that keeps the relevant pods | |||
| // in sync with their corresponding JointInferenceService objects. | |||
| func New(cc *runtime.ControllerContext) (runtime.FeatureControllerI, error) { | |||
| @@ -640,7 +580,7 @@ func New(cc *runtime.ControllerContext) (runtime.FeatureControllerI, error) { | |||
| jc.podStore = podInformer.Lister() | |||
| jc.podStoreSynced = podInformer.Informer().HasSynced | |||
| cc.UpstreamController.Add(KindName, jc.updateFromEdge) | |||
| jc.addUpstreamHandler(cc) | |||
| return jc, nil | |||
| } | |||
| @@ -0,0 +1,92 @@ | |||
| /* | |||
| Copyright 2021 The KubeEdge Authors. | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| */ | |||
| package jointinference | |||
| import ( | |||
| "context" | |||
| "encoding/json" | |||
| "fmt" | |||
| sednav1 "github.com/kubeedge/sedna/pkg/apis/sedna/v1alpha1" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/runtime" | |||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |||
| "k8s.io/klog/v2" | |||
| ) | |||
| func (c *Controller) updateMetrics(name, namespace string, metrics []sednav1.Metric) error { | |||
| client := c.client.JointInferenceServices(namespace) | |||
| return runtime.RetryUpdateStatus(name, namespace, func() error { | |||
| joint, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| joint.Status.Metrics = metrics | |||
| _, err = client.UpdateStatus(context.TODO(), joint, metav1.UpdateOptions{}) | |||
| return err | |||
| }) | |||
| } | |||
| // updateFromEdge syncs the edge updates to k8s | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) error { | |||
| // Output defines owner output information | |||
| type Output struct { | |||
| ServiceInfo map[string]interface{} `json:"ownerInfo"` | |||
| } | |||
| var status struct { | |||
| // Phase always should be "inference" | |||
| Phase string `json:"phase"` | |||
| Status string `json:"status"` | |||
| Output *Output `json:"output"` | |||
| } | |||
| err := json.Unmarshal(content, &status) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // TODO: propagate status.Status to k8s | |||
| output := status.Output | |||
| if output == nil || output.ServiceInfo == nil { | |||
| // no output info | |||
| klog.Warningf("empty status info for joint inference service %s/%s", namespace, name) | |||
| return nil | |||
| } | |||
| info := output.ServiceInfo | |||
| for _, ignoreTimeKey := range []string{ | |||
| "startTime", | |||
| "updateTime", | |||
| } { | |||
| delete(info, ignoreTimeKey) | |||
| } | |||
| metrics := runtime.ConvertMapToMetrics(info) | |||
| err = c.updateMetrics(name, namespace, metrics) | |||
| if err != nil { | |||
| return fmt.Errorf("failed to update metrics, err:%+w", err) | |||
| } | |||
| return nil | |||
| } | |||
| func (c *Controller) addUpstreamHandler(cc *runtime.ControllerContext) error { | |||
| return cc.UpstreamController.Add(KindName, c.updateFromEdge) | |||
| } | |||
| @@ -18,7 +18,6 @@ package lifelonglearning | |||
| import ( | |||
| "context" | |||
| "encoding/json" | |||
| "fmt" | |||
| "strings" | |||
| "time" | |||
| @@ -527,7 +526,7 @@ func (c *Controller) createPod(job *sednav1.LifelongLearningJob, podtype sednav1 | |||
| // get all url for train and eval from data in condition | |||
| condDataStr := job.Status.Conditions[len(job.Status.Conditions)-1].Data | |||
| klog.V(2).Infof("lifelonglearning job %v/%v data condition:%s", job.Namespace, job.Name, condDataStr) | |||
| var cond runtime.LifelongLearningCondData | |||
| var cond LifelongLearningCondData | |||
| (&cond).Unmarshal([]byte(condDataStr)) | |||
| if cond.Input == nil { | |||
| return fmt.Errorf("empty input from condData") | |||
| @@ -704,80 +703,6 @@ func (c *Controller) createInferPod(job *sednav1.LifelongLearningJob) error { | |||
| return err | |||
| } | |||
| func (c *Controller) appendStatusCondition(name, namespace string, cond sednav1.LLJobCondition) error { | |||
| client := c.client.LifelongLearningJobs(namespace) | |||
| return runtime.RetryUpdateStatus(name, namespace, func() error { | |||
| job, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| job.Status.Conditions = append(job.Status.Conditions, cond) | |||
| _, err = client.UpdateStatus(context.TODO(), job, metav1.UpdateOptions{}) | |||
| return err | |||
| }) | |||
| } | |||
| // updateFromEdge syncs the edge updates to k8s | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) error { | |||
| var jobStatus struct { | |||
| Phase string `json:"phase"` | |||
| Status string `json:"status"` | |||
| } | |||
| err := json.Unmarshal(content, &jobStatus) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Get the condition data. | |||
| // Here unmarshal and marshal immediately to skip the unnecessary fields | |||
| var condData runtime.LifelongLearningCondData | |||
| err = json.Unmarshal(content, &condData) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| condDataBytes, _ := json.Marshal(&condData) | |||
| cond := sednav1.LLJobCondition{ | |||
| Status: v1.ConditionTrue, | |||
| LastHeartbeatTime: metav1.Now(), | |||
| LastTransitionTime: metav1.Now(), | |||
| Data: string(condDataBytes), | |||
| Message: "reported by lc", | |||
| } | |||
| switch strings.ToLower(jobStatus.Phase) { | |||
| case "train": | |||
| cond.Stage = sednav1.LLJobTrain | |||
| case "eval": | |||
| cond.Stage = sednav1.LLJobEval | |||
| case "deploy": | |||
| cond.Stage = sednav1.LLJobDeploy | |||
| default: | |||
| return fmt.Errorf("invalid condition stage: %v", jobStatus.Phase) | |||
| } | |||
| switch strings.ToLower(jobStatus.Status) { | |||
| case "ready": | |||
| cond.Type = sednav1.LLJobStageCondReady | |||
| case "completed": | |||
| cond.Type = sednav1.LLJobStageCondCompleted | |||
| case "failed": | |||
| cond.Type = sednav1.LLJobStageCondFailed | |||
| case "waiting": | |||
| cond.Type = sednav1.LLJobStageCondWaiting | |||
| default: | |||
| return fmt.Errorf("invalid condition type: %v", jobStatus.Status) | |||
| } | |||
| err = c.appendStatusCondition(name, namespace, cond) | |||
| if err != nil { | |||
| return fmt.Errorf("failed to append condition, err:%+w", err) | |||
| } | |||
| return nil | |||
| } | |||
| // New creates a new LifelongLearningJob controller that keeps the relevant pods | |||
| // in sync with their corresponding LifelongLearningJob objects. | |||
| func New(cc *runtime.ControllerContext) (runtime.FeatureControllerI, error) { | |||
| @@ -820,7 +745,7 @@ func New(cc *runtime.ControllerContext) (runtime.FeatureControllerI, error) { | |||
| jc.podStore = podInformer.Lister() | |||
| jc.podStoreSynced = podInformer.Informer().HasSynced | |||
| cc.UpstreamController.Add(KindName, jc.updateFromEdge) | |||
| jc.addUpstreamHandler(cc) | |||
| return jc, nil | |||
| } | |||
| @@ -0,0 +1,164 @@ | |||
| /* | |||
| Copyright 2021 The KubeEdge Authors. | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| */ | |||
| package lifelonglearning | |||
| import ( | |||
| "context" | |||
| "encoding/json" | |||
| "fmt" | |||
| "strings" | |||
| sednav1 "github.com/kubeedge/sedna/pkg/apis/sedna/v1alpha1" | |||
| v1 "k8s.io/api/core/v1" | |||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/runtime" | |||
| ) | |||
| type Model = runtime.Model | |||
| // the data of this condition including the input/output to do the next step | |||
| type LifelongLearningCondData struct { | |||
| Input *struct { | |||
| // Only one model cases | |||
| Model *Model `json:"model,omitempty"` | |||
| Models []Model `json:"models,omitempty"` | |||
| DataURL string `json:"dataURL,omitempty"` | |||
| // the data samples reference will be stored into this URL. | |||
| // The content of this url would be: | |||
| // # the first uncomment line means the directory | |||
| // s3://dataset/ | |||
| // mnist/0.jpg | |||
| // mnist/1.jpg | |||
| DataIndexURL string `json:"dataIndexURL,omitempty"` | |||
| OutputDir string `json:"outputDir,omitempty"` | |||
| } `json:"input,omitempty"` | |||
| Output *struct { | |||
| Model *Model `json:"model,omitempty"` | |||
| Models []Model `json:"models,omitempty"` | |||
| } `json:"output,omitempty"` | |||
| } | |||
| func (cd *LifelongLearningCondData) joinModelURLs(model *Model, models []Model) []string { | |||
| var modelURLs []string | |||
| if model != nil { | |||
| modelURLs = append(modelURLs, model.GetURL()) | |||
| } else { | |||
| for _, m := range models { | |||
| modelURLs = append(modelURLs, m.GetURL()) | |||
| } | |||
| } | |||
| return modelURLs | |||
| } | |||
| func (cd *LifelongLearningCondData) Unmarshal(data []byte) error { | |||
| return json.Unmarshal(data, cd) | |||
| } | |||
| func (cd LifelongLearningCondData) Marshal() ([]byte, error) { | |||
| return json.Marshal(cd) | |||
| } | |||
| func (cd *LifelongLearningCondData) GetInputModelURLs() []string { | |||
| return cd.joinModelURLs(cd.Input.Model, cd.Input.Models) | |||
| } | |||
| func (cd *LifelongLearningCondData) GetOutputModelURLs() []string { | |||
| return cd.joinModelURLs(cd.Output.Model, cd.Output.Models) | |||
| } | |||
| func (c *Controller) appendStatusCondition(name, namespace string, cond sednav1.LLJobCondition) error { | |||
| client := c.client.LifelongLearningJobs(namespace) | |||
| return runtime.RetryUpdateStatus(name, namespace, func() error { | |||
| job, err := client.Get(context.TODO(), name, metav1.GetOptions{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| job.Status.Conditions = append(job.Status.Conditions, cond) | |||
| _, err = client.UpdateStatus(context.TODO(), job, metav1.UpdateOptions{}) | |||
| return err | |||
| }) | |||
| } | |||
| // updateFromEdge syncs the edge updates to k8s | |||
| func (c *Controller) updateFromEdge(name, namespace, operation string, content []byte) error { | |||
| var jobStatus struct { | |||
| Phase string `json:"phase"` | |||
| Status string `json:"status"` | |||
| } | |||
| err := json.Unmarshal(content, &jobStatus) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Get the condition data. | |||
| // Here unmarshal and marshal immediately to skip the unnecessary fields | |||
| var condData LifelongLearningCondData | |||
| err = json.Unmarshal(content, &condData) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| condDataBytes, _ := json.Marshal(&condData) | |||
| cond := sednav1.LLJobCondition{ | |||
| Status: v1.ConditionTrue, | |||
| LastHeartbeatTime: metav1.Now(), | |||
| LastTransitionTime: metav1.Now(), | |||
| Data: string(condDataBytes), | |||
| Message: "reported by lc", | |||
| } | |||
| switch strings.ToLower(jobStatus.Phase) { | |||
| case "train": | |||
| cond.Stage = sednav1.LLJobTrain | |||
| case "eval": | |||
| cond.Stage = sednav1.LLJobEval | |||
| case "deploy": | |||
| cond.Stage = sednav1.LLJobDeploy | |||
| default: | |||
| return fmt.Errorf("invalid condition stage: %v", jobStatus.Phase) | |||
| } | |||
| switch strings.ToLower(jobStatus.Status) { | |||
| case "ready": | |||
| cond.Type = sednav1.LLJobStageCondReady | |||
| case "completed": | |||
| cond.Type = sednav1.LLJobStageCondCompleted | |||
| case "failed": | |||
| cond.Type = sednav1.LLJobStageCondFailed | |||
| case "waiting": | |||
| cond.Type = sednav1.LLJobStageCondWaiting | |||
| default: | |||
| return fmt.Errorf("invalid condition type: %v", jobStatus.Status) | |||
| } | |||
| err = c.appendStatusCondition(name, namespace, cond) | |||
| if err != nil { | |||
| return fmt.Errorf("failed to append condition, err:%+w", err) | |||
| } | |||
| return nil | |||
| } | |||
| func (c *Controller) addUpstreamHandler(cc *runtime.ControllerContext) error { | |||
| return cc.UpstreamController.Add(KindName, c.updateFromEdge) | |||
| } | |||
| @@ -17,8 +17,6 @@ limitations under the License. | |||
| package controllers | |||
| import ( | |||
| "fmt" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/controllers/dataset" | |||
| fl "github.com/kubeedge/sedna/pkg/globalmanager/controllers/federatedlearning" | |||
| il "github.com/kubeedge/sedna/pkg/globalmanager/controllers/incrementallearning" | |||
| @@ -32,7 +32,7 @@ type UpstreamController struct { | |||
| updateHandlers map[string]runtime.UpstreamUpdateHandler | |||
| } | |||
| func (uc *UpstreamController)checkOperation(operation string) error { | |||
| func (uc *UpstreamController) checkOperation(operation string) error { | |||
| // current only support the 'status' operation | |||
| if operation != "status" { | |||
| return fmt.Errorf("unknown operation %s", operation) | |||
| @@ -52,13 +52,12 @@ func (uc *UpstreamController) syncEdgeUpdate() { | |||
| update, err := uc.messageLayer.ReceiveResourceUpdate() | |||
| if err == nil { | |||
| err = uc.checkOperation(update.operation) | |||
| err = uc.checkOperation(update.Operation) | |||
| } | |||
| if err != nil && err := { | |||
| if err != nil { | |||
| klog.Warningf("Ignore update since this err: %+v", err) | |||
| continue | |||
| } | |||
| if err != nil | |||
| kind := update.Kind | |||
| namespace := update.Namespace | |||
| @@ -17,8 +17,6 @@ limitations under the License. | |||
| package runtime | |||
| import ( | |||
| "encoding/json" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/config" | |||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |||
| k8sruntime "k8s.io/apimachinery/pkg/runtime" | |||
| @@ -48,32 +46,6 @@ type Model struct { | |||
| Metrics map[string]interface{} `json:"metrics,omitempty"` | |||
| } | |||
| // the data of this condition including the input/output to do the next step | |||
| type IncrementalCondData struct { | |||
| Input *struct { | |||
| // Only one model cases | |||
| Model *Model `json:"model,omitempty"` | |||
| Models []Model `json:"models,omitempty"` | |||
| DataURL string `json:"dataURL,omitempty"` | |||
| // the data samples reference will be stored into this URL. | |||
| // The content of this url would be: | |||
| // # the first uncomment line means the directory | |||
| // s3://dataset/ | |||
| // mnist/0.jpg | |||
| // mnist/1.jpg | |||
| DataIndexURL string `json:"dataIndexURL,omitempty"` | |||
| OutputDir string `json:"outputDir,omitempty"` | |||
| } `json:"input,omitempty"` | |||
| Output *struct { | |||
| Model *Model `json:"model,omitempty"` | |||
| Models []Model `json:"models,omitempty"` | |||
| } `json:"output,omitempty"` | |||
| } | |||
| const ( | |||
| // TrainPodType is type of train pod | |||
| TrainPodType = "train" | |||
| @@ -90,88 +62,6 @@ func (m *Model) GetURL() string { | |||
| return m.URL | |||
| } | |||
| func (cd *IncrementalCondData) joinModelURLs(model *Model, models []Model) []string { | |||
| var modelURLs []string | |||
| if model != nil { | |||
| modelURLs = append(modelURLs, model.GetURL()) | |||
| } else { | |||
| for _, m := range models { | |||
| modelURLs = append(modelURLs, m.GetURL()) | |||
| } | |||
| } | |||
| return modelURLs | |||
| } | |||
| func (cd *IncrementalCondData) GetInputModelURLs() []string { | |||
| return cd.joinModelURLs(cd.Input.Model, cd.Input.Models) | |||
| } | |||
| func (cd *IncrementalCondData) GetOutputModelURLs() []string { | |||
| return cd.joinModelURLs(cd.Output.Model, cd.Output.Models) | |||
| } | |||
| func (cd *IncrementalCondData) Unmarshal(data []byte) error { | |||
| return json.Unmarshal(data, cd) | |||
| } | |||
| func (cd IncrementalCondData) Marshal() ([]byte, error) { | |||
| return json.Marshal(cd) | |||
| } | |||
| // the data of this condition including the input/output to do the next step | |||
| type LifelongLearningCondData struct { | |||
| Input *struct { | |||
| // Only one model cases | |||
| Model *Model `json:"model,omitempty"` | |||
| Models []Model `json:"models,omitempty"` | |||
| DataURL string `json:"dataURL,omitempty"` | |||
| // the data samples reference will be stored into this URL. | |||
| // The content of this url would be: | |||
| // # the first uncomment line means the directory | |||
| // s3://dataset/ | |||
| // mnist/0.jpg | |||
| // mnist/1.jpg | |||
| DataIndexURL string `json:"dataIndexURL,omitempty"` | |||
| OutputDir string `json:"outputDir,omitempty"` | |||
| } `json:"input,omitempty"` | |||
| Output *struct { | |||
| Model *Model `json:"model,omitempty"` | |||
| Models []Model `json:"models,omitempty"` | |||
| } `json:"output,omitempty"` | |||
| } | |||
| func (cd *LifelongLearningCondData) joinModelURLs(model *Model, models []Model) []string { | |||
| var modelURLs []string | |||
| if model != nil { | |||
| modelURLs = append(modelURLs, model.GetURL()) | |||
| } else { | |||
| for _, m := range models { | |||
| modelURLs = append(modelURLs, m.GetURL()) | |||
| } | |||
| } | |||
| return modelURLs | |||
| } | |||
| func (cd *LifelongLearningCondData) Unmarshal(data []byte) error { | |||
| return json.Unmarshal(data, cd) | |||
| } | |||
| func (cd LifelongLearningCondData) Marshal() ([]byte, error) { | |||
| return json.Marshal(cd) | |||
| } | |||
| func (cd *LifelongLearningCondData) GetInputModelURLs() []string { | |||
| return cd.joinModelURLs(cd.Input.Model, cd.Input.Models) | |||
| } | |||
| func (cd *LifelongLearningCondData) GetOutputModelURLs() []string { | |||
| return cd.joinModelURLs(cd.Output.Model, cd.Output.Models) | |||
| } | |||
| // updateHandler handles the updates from LC(running at edge) to update the | |||
| // corresponding resource | |||
| type UpstreamUpdateHandler func(namespace, name, operation string, content []byte) error | |||
| @@ -31,6 +31,7 @@ import ( | |||
| "github.com/kubeedge/sedna/cmd/sedna-lc/app/options" | |||
| sednav1 "github.com/kubeedge/sedna/pkg/apis/sedna/v1alpha1" | |||
| gmtypes "github.com/kubeedge/sedna/pkg/globalmanager/controllers/incrementallearning" | |||
| "github.com/kubeedge/sedna/pkg/globalmanager/runtime" | |||
| "github.com/kubeedge/sedna/pkg/localcontroller/db" | |||
| "github.com/kubeedge/sedna/pkg/localcontroller/gmclient" | |||
| @@ -441,7 +442,7 @@ func (im *IncrementalJobManager) getTrainOrEvalModel(job *IncrementalLearningJob | |||
| var models []runtime.Model | |||
| for i := len(jobConditions) - 1; i >= 0; i-- { | |||
| var cond runtime.IncrementalCondData | |||
| var cond gmtypes.IncrementalCondData | |||
| jobCond := jobConditions[i] | |||
| if jobCond.Stage == sednav1.ILJobTrain && jobCond.Type == sednav1.ILJobStageCondCompleted { | |||
| if err := (&cond).Unmarshal([]byte(jobCond.Data)); err != nil { | |||