Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): Implement RuntimeConfig in backend #8085

Merged
merged 30 commits into from
Aug 11, 2022

Conversation

Linchin
Copy link
Contributor

@Linchin Linchin commented Jul 30, 2022

Description of your changes:

  • Implemented for runs only.

Checklist:

@google-oss-prow google-oss-prow bot added size/XL and removed size/L labels Aug 2, 2022
runDetail.PipelineSpecManifest = manifest
runDetail.PipelineSpec.RuntimeConfig.Parameters = params
runDetail.PipelineSpec.RuntimeConfig.PipelineRoot = run.GetPipelineSpec().GetRuntimeConfig().GetPipelineRoot()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you gofmt the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do!

@@ -32,4 +32,7 @@ type PipelineSpec struct {

// Store parameters key-value pairs as serialized string.
Parameters string `gorm:"column:Parameters; size:65535"`

// Runtime config of the pipeline
RuntimeConfig
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - can the parameters be stored in the field above, and just add a new field for storing pipeline root?
the DB schema doesn't have to be 1:1 mapping with API schema

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and no matter which way to go, call out in the comment which fields are v1 only and v2 only.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was discussed in the design review. The main reason behind it is to eliminate the confusion between v1 and v2 parameters. I also added comments to clarify the difference between the two Parameters fields.

for key := range runtimeConfig.GetParameters() {
keys = append(keys, key)
}
sort.Strings(keys)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why sorting the keys?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the Parameters is a map, the marshalled JSON may be of any random order. I sorted the keys for mainly two reasons:

  1. To make comparisons of strings in testing possible
  2. Ensure the marshalled JSONs are the same whenever the Parameters are the same

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for #1, as a eng practice, the test shouldn't affect the source code. can you unmarshal the string field and compare without ordering in test code?
for #2, is that necessary for the BE to work properly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to both points raised by Yang.

In fact, I don't see how sorting the list of keys affects the serialization of the dict.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for #1, since we are marshalling the dict into a string, the key-value pairs may be marshalled in any random order. This makes the resulting string possibly different every time, thus making testing by comparing the strings directly impossible. An alternative to comparing strings is to unmarshal the strings back into maps, and compare the maps instead. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i think that's one way to do this correctly.

@@ -253,28 +256,31 @@ func runtimeConfigToModelParameters(runtimeConfig *api.PipelineSpec_RuntimeConfi
return "", nil
}
var params []v1alpha1.Parameter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the code is confusing to me. the runtime config is a v2 proto, but here we marshall the runtime config into a string through a v1alpha1 API proto.

Can we start refactoring these code and separate out the v1 and v2 code path, making them hermetically avoiding referring to the proto in the other version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a good point! I will update the code accordingly.

@google-oss-prow google-oss-prow bot added size/XXL and removed size/XL labels Aug 4, 2022
api/v2alpha1/go/pipelinespec/pipeline_spec.pb.go Outdated Show resolved Hide resolved
for key := range runtimeConfig.GetParameters() {
keys = append(keys, key)
}
sort.Strings(keys)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to both points raised by Yang.

In fact, I don't see how sorting the list of keys affects the serialization of the dict.

backend/src/apiserver/server/api_converter.go Outdated Show resolved Hide resolved
@@ -148,14 +152,45 @@ func toApiParameters(paramsString string) ([]*api.Parameter, error) {
return apiParams, nil
}

func toApiRuntimeParameters(paramsString string) (map[string]*structpb.Value, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have unit tests for newly added methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a new unit test involving this method, though there is no individual unit test for this single method. Should I add unit tests at this level?

@@ -148,14 +152,45 @@ func toApiParameters(paramsString string) ([]*api.Parameter, error) {
return apiParams, nil
}

func toApiRuntimeParameters(paramsString string) (map[string]*structpb.Value, error) {
if paramsString == "" {
return make(map[string]*structpb.Value), nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to return an empty dict?

@@ -148,14 +152,45 @@ func toApiParameters(paramsString string) ([]*api.Parameter, error) {
return apiParams, nil
}

func toApiRuntimeParameters(paramsString string) (map[string]*structpb.Value, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we talked about this offline, why not just have toApiRuntimeConfig that returns api.PipelineSpec_RuntimeConfig?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We indeed talked about it offline, but that was for parseXXX functions in run_store.go (link). Here toApiRuntimeParameters() is analogous to toApiParameters() above (link).

@@ -81,6 +86,71 @@ func TestCreateRun(t *testing.T) {
assert.Equal(t, expectedRunDetail, *runDetail)
}

/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

}
runtimeParams := make(map[string]*structpb.Value)
for k, v := range runtimeParamsStringsMap {
protoValue := structpb.NewStringValue("")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why NewStringValue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to initialize the variable to be later marshalled into.

WorkflowManifest: "workflow123",
},
}
assert.Equal(t, expectedApiRun.String(), apiRun.String())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for Sting()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added explanation for usage of String(). Some fields in ApiRuns are only used for protobuff, and may be different although the fields we use are the same. Using string representation avoids the issue.

This reverts commit 251c3a9.
@google-oss-prow google-oss-prow bot added size/XL and removed size/XXL labels Aug 4, 2022
@Linchin Linchin requested review from IronPan and chensun August 5, 2022 19:49
Copy link
Member

@chensun chensun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

backend/src/apiserver/server/api_converter.go Outdated Show resolved Hide resolved

// Test all parameters types converted to model.RuntimeConfig.Parameters, which is string type
v2RuntimeParams := map[string]*structpb.Value{
"param2": structpb.NewStringValue("world"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why starting with 2?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why, but all test cases in api_converter_test.go have parameters starting at 2.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal yet it just reads odd. I really don't see why this needs to be repeated or reinforced.

backend/src/apiserver/server/api_converter_test.go Outdated Show resolved Hide resolved
backend/src/apiserver/server/api_converter_test.go Outdated Show resolved Hide resolved
@google-oss-prow google-oss-prow bot removed the lgtm label Aug 8, 2022
Copy link
Member

@chensun chensun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm


// Test all parameters types converted to model.RuntimeConfig.Parameters, which is string type
v2RuntimeParams := map[string]*structpb.Value{
"param2": structpb.NewStringValue("world"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal yet it just reads odd. I really don't see why this needs to be repeated or reinforced.

@google-oss-prow google-oss-prow bot added the lgtm label Aug 8, 2022
modelJob.PipelineSpecManifest = manifest
modelJob.PipelineSpec.RuntimeConfig.Parameters = params
modelJob.PipelineSpec.RuntimeConfig.PipelineRoot = job.GetPipelineSpec().GetRuntimeConfig().GetPipelineRoot()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you gofmt the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did gofmt for the single file but nothing changed. I will create a new PR to run the command over the entire backend codebase, so we only have changes related to runtimeconfig in this PR.

@@ -253,34 +255,19 @@ func runtimeConfigToModelParameters(runtimeConfig *api.PipelineSpec_RuntimeConfi
if runtimeConfig == nil {
return "", nil
}
var params util.SpecParameters
// Use structpb to marshal protobuff value, store them in a map, then marshal the entire map into a single new string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is this necessary. Can you just json marshal the runtimeConfig.Parameters of type map[string]*structpb.Value without marshaling the value first?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. I updated the code accordingly.

Comment on lines 163 to 170
runtimeParams = make(map[string]*structpb.Value)
for k, v := range runtimeParamsStringsMap {
var protoValue structpb.Value
err := protoValue.UnmarshalJSON([]byte(v))
if err != nil {
return nil, util.NewInternalServerError(err, fmt.Sprintf("Cannot unmarshal string %+v to structpb.Value", v))
}
runtimeParams[k] = &protoValue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, for unmarshal, is it possible to do it in 1 step instead of unmarshal the value first then unmarshal the map?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it was necessary. I have updated the code accordingly.

Comment on lines +181 to +196
// v1 parameters
params, err := toApiParameters(run.Parameters)
if err != nil {
return &api.Run{
Id: run.UUID,
Error: err.Error(),
}
}
// v2 RuntimeConfig
runtimeConfig, err := toApiRuntimeConfig(run.PipelineSpec.RuntimeConfig)
if err != nil {
return &api.Run{
Id: run.UUID,
Error: err.Error(),
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still feeling a bit strong about this.
we should really create a new v2 pipeline_spec.proto instead of reusing v1 API in
https://github.com/kubeflow/pipelines/blob/master/backend/api/pipeline_spec.proto

To note, anything we released to post Beta is subject to the formal deprecation policy. If I understand correctly this will be released as part of the next V1 release which is already GA'ed? (Or is the API proto already released?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that being said i'm ok with merge this and do a quick follow up PR to properly break down the code before the next backward compatible promised release.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the current API that has been released (which is going to be V1 going forward) already contains RuntimeConfig field. FE uses RuntimeConfig to pass parameters and pipeline roots, where only parameters are stored in the backend DB. Then backend returns the run without using the RuntimeConfig field. The change here just makes sure the RuntimeConfig info coming from UI is stored, and then can be returned via RuntimeConfig. Shall we remove RuntimeConfig from V1 entirely after releasing V2?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes let's call out that in the proto comment, and do that when adding v2 api.

@google-oss-prow google-oss-prow bot removed the lgtm label Aug 10, 2022
@Linchin Linchin requested review from chensun and IronPan August 11, 2022 00:26
@IronPan
Copy link
Member

IronPan commented Aug 11, 2022

/lgtm
/approve

@google-oss-prow
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: IronPan

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@google-oss-prow google-oss-prow bot merged commit 2b556ec into master Aug 11, 2022
@google-oss-prow google-oss-prow bot deleted the lingqing-runtimeconfig-0730 branch August 11, 2022 23:21
bokleung pushed a commit to bokleung/pipelines that referenced this pull request Aug 15, 2022
* add runtime config model

* add run store and tests

* add runtime_config to model converter and tests

* add runtime_config to api converter and its tests

* change api server and related tests

* remove v2 runtime_config test

* add runtimeconfig to upgrade test

* fix test values

* upgrade test debug

* tests

* add more info for debug

* use NullString instead of String, remove debug

* fix type conversion

* change function and add unit tests

* run go fmt

* Add comments for model

* marshal params using v2 structpb values

* fix small bug

* Revert "run go fmt"

This reverts commit 251c3a9.

* No longer sort keys

* test values and explain comparison using .String()

* func toApiRuntimeConfig

* tests updates

* add api converter tests

* change test

* fix format

* change test

* simplify marshalling parameters
jlyaoyuli pushed a commit to jlyaoyuli/pipelines that referenced this pull request Jan 5, 2023
* add runtime config model

* add run store and tests

* add runtime_config to model converter and tests

* add runtime_config to api converter and its tests

* change api server and related tests

* remove v2 runtime_config test

* add runtimeconfig to upgrade test

* fix test values

* upgrade test debug

* tests

* add more info for debug

* use NullString instead of String, remove debug

* fix type conversion

* change function and add unit tests

* run go fmt

* Add comments for model

* marshal params using v2 structpb values

* fix small bug

* Revert "run go fmt"

This reverts commit 251c3a9.

* No longer sort keys

* test values and explain comparison using .String()

* func toApiRuntimeConfig

* tests updates

* add api converter tests

* change test

* fix format

* change test

* simplify marshalling parameters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants