Browse Source

gm: split all upstream logic into separate file

Signed-off-by: llhuii <liulinghui@huawei.com>
tags/v0.3.1
llhuii 4 years ago
parent
commit
e52ac06ed1
15 changed files with 650 additions and 464 deletions
  1. +19
    -40
      pkg/globalmanager/controllers/dataset/dataset.go
  2. +17
    -0
      pkg/globalmanager/controllers/dataset/downstream.go
  3. +62
    -0
      pkg/globalmanager/controllers/dataset/upstream.go
  4. +1
    -94
      pkg/globalmanager/controllers/federatedlearning/federatedlearningjob.go
  5. +123
    -0
      pkg/globalmanager/controllers/federatedlearning/upstream.go
  6. +2
    -75
      pkg/globalmanager/controllers/incrementallearning/incrementallearningjob.go
  7. +162
    -0
      pkg/globalmanager/controllers/incrementallearning/upstream.go
  8. +1
    -61
      pkg/globalmanager/controllers/jointinference/jointinferenceservice.go
  9. +92
    -0
      pkg/globalmanager/controllers/jointinference/upstream.go
  10. +2
    -77
      pkg/globalmanager/controllers/lifelonglearning/lifelonglearningjob.go
  11. +164
    -0
      pkg/globalmanager/controllers/lifelonglearning/upstream.go
  12. +0
    -2
      pkg/globalmanager/controllers/registry.go
  13. +3
    -4
      pkg/globalmanager/controllers/upstream.go
  14. +0
    -110
      pkg/globalmanager/runtime/types.go
  15. +2
    -1
      pkg/localcontroller/manager/incrementallearningjob.go

+ 19
- 40
pkg/globalmanager/controllers/dataset/dataset.go View File

@@ -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
}

+ 17
- 0
pkg/globalmanager/controllers/dataset/downstream.go View File

@@ -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

+ 62
- 0
pkg/globalmanager/controllers/dataset/upstream.go View File

@@ -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)
}

+ 1
- 94
pkg/globalmanager/controllers/federatedlearning/federatedlearningjob.go View File

@@ -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
}

+ 123
- 0
pkg/globalmanager/controllers/federatedlearning/upstream.go View File

@@ -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)
}

+ 2
- 75
pkg/globalmanager/controllers/incrementallearning/incrementallearningjob.go View File

@@ -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
}

+ 162
- 0
pkg/globalmanager/controllers/incrementallearning/upstream.go View File

@@ -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)
}

+ 1
- 61
pkg/globalmanager/controllers/jointinference/jointinferenceservice.go View File

@@ -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
}

+ 92
- 0
pkg/globalmanager/controllers/jointinference/upstream.go View File

@@ -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)
}

+ 2
- 77
pkg/globalmanager/controllers/lifelonglearning/lifelonglearningjob.go View File

@@ -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
}

+ 164
- 0
pkg/globalmanager/controllers/lifelonglearning/upstream.go View File

@@ -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)
}

+ 0
- 2
pkg/globalmanager/controllers/registry.go View File

@@ -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"


+ 3
- 4
pkg/globalmanager/controllers/upstream.go View File

@@ -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


+ 0
- 110
pkg/globalmanager/runtime/types.go View File

@@ -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


+ 2
- 1
pkg/localcontroller/manager/incrementallearningjob.go View File

@@ -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 {


Loading…
Cancel
Save