-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Conversation
runDetail.PipelineSpecManifest = manifest | ||
runDetail.PipelineSpec.RuntimeConfig.Parameters = params | ||
runDetail.PipelineSpec.RuntimeConfig.PipelineRoot = run.GetPipelineSpec().GetRuntimeConfig().GetPipelineRoot() |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why sorting the keys?
There was a problem hiding this comment.
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:
- To make comparisons of strings in testing possible
- Ensure the marshalled JSONs are the same whenever the
Parameters
are the same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
for key := range runtimeConfig.GetParameters() { | ||
keys = append(keys, key) | ||
} | ||
sort.Strings(keys) |
There was a problem hiding this comment.
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.
@@ -148,14 +152,45 @@ func toApiParameters(paramsString string) ([]*api.Parameter, error) { | |||
return apiParams, nil | |||
} | |||
|
|||
func toApiRuntimeParameters(paramsString string) (map[string]*structpb.Value, error) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -81,6 +86,71 @@ func TestCreateRun(t *testing.T) { | |||
assert.Equal(t, expectedRunDetail, *runDetail) | |||
} | |||
|
|||
/* |
There was a problem hiding this comment.
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("") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why NewStringValue
?
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for Sting()
There was a problem hiding this comment.
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.
There was a problem hiding this 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"), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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"), |
There was a problem hiding this comment.
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.
modelJob.PipelineSpecManifest = manifest | ||
modelJob.PipelineSpec.RuntimeConfig.Parameters = params | ||
modelJob.PipelineSpec.RuntimeConfig.PipelineRoot = job.GetPipelineSpec().GetRuntimeConfig().GetPipelineRoot() |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
// 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(), | ||
} | ||
} |
There was a problem hiding this comment.
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?)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
/lgtm |
[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 |
* 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
* 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
Description of your changes:
Checklist: