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

add plugin runtime API #22469

Merged
merged 24 commits into from
Aug 31, 2023
Merged

add plugin runtime API #22469

merged 24 commits into from
Aug 31, 2023

Conversation

thyton
Copy link
Contributor

@thyton thyton commented Aug 21, 2023

Implement plugin runtime CRUD API

@github-actions github-actions bot added the hashicorp-contributed-pr If the PR is HashiCorp (i.e. not-community) contributed label Aug 21, 2023
@thyton thyton requested a review from tomhjp August 21, 2023 16:34
@thyton thyton marked this pull request as draft August 21, 2023 16:34
@github-actions
Copy link

Build Results:
All builds succeeded! ✅

@github-actions
Copy link

github-actions bot commented Aug 21, 2023

CI Results:
All Go tests succeeded! ✅

Copy link
Contributor

@tomhjp tomhjp left a comment

Choose a reason for hiding this comment

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

Looking good! LMK any thoughts on the API path comments. This is a good sized unit of work to land so definitely save the CLI changes for another PR. Feel free to ping me when the TODOs are tidied up (or earlier if you prefer) and I can take another more detailed pass.

vault/logical_system_paths.go Outdated Show resolved Hide resolved
vault/logical_system_paths.go Show resolved Hide resolved
vault/logical_system.go Show resolved Hide resolved
sdk/helper/pluginruntimeutil/runner.go Outdated Show resolved Hide resolved
Copy link
Contributor

@tomhjp tomhjp left a comment

Choose a reason for hiding this comment

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

Mostly nits and follow ups. The only substantive comments are to replace float with integer types throughout (unless I'm missing something) and deleting some APIs that relate to backwards compatibility baggage from the plugin catalog. Otherwise, LGTM!

vault/plugin_runtime_catalog.go Show resolved Hide resolved
sdk/helper/pluginruntimeutil/config.go Outdated Show resolved Hide resolved
vault/plugin_runtime_catalog.go Outdated Show resolved Hide resolved
vault/plugin_runtime_catalog.go Outdated Show resolved Hide resolved
return ErrPluginRuntimeNotFound
}

// TODO what happens to the running existing container with(out) running plugins. Need to shut it down before setting it?
Copy link
Contributor

Choose a reason for hiding this comment

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

The implementation doesn't belong in this PR, but I think we should handle this case higher up in the call stack - i.e. in the delete API handler function.

vault/logical_system_paths.go Show resolved Hide resolved
vault/logical_system_paths.go Outdated Show resolved Hide resolved
vault/logical_system.go Outdated Show resolved Hide resolved
vault/logical_system.go Outdated Show resolved Hide resolved
api/sys_plugins_runtimes.go Outdated Show resolved Hide resolved
@thyton thyton added this to the 1.15 milestone Aug 30, 2023
@thyton thyton marked this pull request as ready for review August 31, 2023 01:09
Comment on lines 164 to 172
runtimesJSON, err := json.Marshal(secret.Data["runtimes"])
if err != nil {
return nil, fmt.Errorf("unable to marshal runtimes")
}

var runtimes []PluginRuntimeDetails
if err = json.Unmarshal(runtimesJSON, &runtimes); err != nil {
return nil, fmt.Errorf("unable to parse runtimes")
}
Copy link
Contributor Author

@thyton thyton Aug 31, 2023

Choose a reason for hiding this comment

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

I had a bit of struggle parsing the Data back to runtimes after changing to store the returned runtimes in Data as []map[string]any

var data []map[string]any
for _, runtimeType := range consts.PluginRuntimeTypes {
if runtimeType == consts.PluginRuntimeTypeUnsupported {
continue
}
configs, err := b.Core.pluginRuntimeCatalog.List(ctx, runtimeType)
if err != nil {
return nil, err
}
if len(configs) > 0 {
sort.Slice(configs, func(i, j int) bool {
return strings.Compare(configs[i].Name, configs[j].Name) == -1
})
for _, conf := range configs {
data = append(data, map[string]any{
"name": conf.Name,
"type": conf.Type.String(),
"oci_runtime": conf.OCIRuntime,
"cgroup_parent": conf.CgroupParent,
"cpu": conf.CPU,
"memory": conf.Memory,
})
}
}
}
resp := &logical.Response{
Data: map[string]interface{}{},
}
if len(data) > 0 {
resp.Data["runtimes"] = data
}
return resp, nil
}
like in the example Response below.

{
    "request_id": "e93d3f93-8e4f-8443-a803-f1c97c123456",
    "data": {
        "runtimes": [
			{
				"name": "gvisor",
				"type": "container",
				"oci_runtime": "runsc",
				"cgroup_parent": "/cpulimit/",
				"cpu": 1,
				"memory": 10000
			},
			{
				"name": "foo",
				"type": "container",
				"oci_runtime": "otherociruntime",
				"cgroup_parent": "/memorylimit/",
				"cpu": 2,
				"memory": 20000
			}
		]
    },
    "warnings": null,
    "auth": null
}

I attempted use mapstructure.Decode different ways to parse Response.Data["runtimes"]
but TestList* sys_plugins_runtimes_test.go couldn't pass asOCIRuntime:"", CgroupParent:"" weren't parsed correctly.

=== RUN   TestListPluginRuntimeTyped/container
    sys_plugins_runtimes_test.go:135: expected: &api.ListPluginRuntimesResponse{Runtimes:[]api.PluginRuntimeDetails{api.PluginRuntimeDetails{Type:"container", Name:"gvisor", OCIRuntime:"runsc", CgroupParent:"/cpulimit/", CPU:1, Memory:10000}}}
        got: &api.ListPluginRuntimesResponse{Runtimes:[]api.PluginRuntimeDetails{api.PluginRuntimeDetails{Type:"container", Name:"gvisor", OCIRuntime:"", CgroupParent:"", CPU:1, Memory:10000}}}
=== RUN   TestListPluginRuntimeTyped/unsupported
--- FAIL: TestListPluginRuntimeTyped (0.01s)

=== RUN   TestListPluginRuntimeUntyped/#00
    sys_plugins_runtimes_test.go:197: expected: &api.ListPluginRuntimesResponse{Runtimes:[]api.PluginRuntimeDetails{api.PluginRuntimeDetails{Type:"container", Name:"gvisor", OCIRuntime:"runsc", CgroupParent:"/cpulimit/", CPU:1, Memory:10000}, api.PluginRuntimeDetails{Type:"container", Name:"foo", OCIRuntime:"otherociruntime", CgroupParent:"/memorylimit/", CPU:2, Memory:20000}, api.PluginRuntimeDetails{Type:"container", Name:"bar", OCIRuntime:"otherociruntime", CgroupParent:"/cpulimit/", CPU:3, Memory:30000}}}
        got: &api.ListPluginRuntimesResponse{Runtimes:[]api.PluginRuntimeDetails{api.PluginRuntimeDetails{Type:"container", Name:"gvisor", OCIRuntime:"", CgroupParent:"", CPU:1, Memory:10000}, api.PluginRuntimeDetails{Type:"container", Name:"foo", OCIRuntime:"", CgroupParent:"", CPU:2, Memory:20000}, api.PluginRuntimeDetails{Type:"container", Name:"bar", OCIRuntime:"", CgroupParent:"", CPU:3, Memory:30000}}}
--- FAIL: TestListPluginRuntimeUntyped (0.01s)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After mapstructure.Decode attemps, I resorted to use json.Marshal and json.Unmarshal to put Data["runtimes"] in JSON bytes and let the library do the work, and the tests pass now. Admittedly, I feel this is a bit weird and hacky way. I'm open to any thoughts and tips that you have.

Copy link
Contributor

Choose a reason for hiding this comment

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

The "bug" here was that mapstructure didn't correctly guess the translation between field name and JSON key. You can give it explicit instructions in 2 ways: either create a mapstructure.DecoderConfig and set the TagName to "json" so that it uses the json tags instead of its default "mapstructure". Or you can set the mapstructure keys in the struct like so:

type PluginRuntimeDetails struct {
	Type         string `json:"type" mapstructure:"type"`
	Name         string `json:"name" mapstructure:"name"`
	OCIRuntime   string `json:"oci_runtime" mapstructure:"oci_runtime"`
	CgroupParent string `json:"cgroup_parent" mapstructure:"cgroup_parent"`
	CPU          int64  `json:"cpu" mapstructure:"cpu"`
	Memory       int64  `json:"memory" mapstructure:"memory"`
}

If you go with the latter method, the tests pass with this as the decoding step, which is probably my personal preference because the "interesting" code is more straightforward:

	if err = mapstructure.Decode(secret.Data["runtimes"], &runtimes); err != nil {
		return nil, fmt.Errorf("unable to parse runtimes")
	}

Setting DecoderConfig looks like this:

	decoderConfig := mapstructure.DecoderConfig{
		TagName: "json",
		Result:  &runtimes,
	}
	decoder, err := mapstructure.NewDecoder(&decoderConfig)
	if err != nil {
		return nil, err
	}
	if err = decoder.Decode(secret.Data["runtimes"]); err != nil {
		return nil, fmt.Errorf("unable to parse runtimes")
	}

@thyton thyton requested a review from tomhjp August 31, 2023 02:02
Copy link
Contributor

@tomhjp tomhjp left a comment

Choose a reason for hiding this comment

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

LGTM! One potential typo and just nits otherwise

api/sys_plugins_runtimes.go Outdated Show resolved Hide resolved
api/sys_plugins_runtimes.go Outdated Show resolved Hide resolved
Comment on lines 164 to 172
runtimesJSON, err := json.Marshal(secret.Data["runtimes"])
if err != nil {
return nil, fmt.Errorf("unable to marshal runtimes")
}

var runtimes []PluginRuntimeDetails
if err = json.Unmarshal(runtimesJSON, &runtimes); err != nil {
return nil, fmt.Errorf("unable to parse runtimes")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The "bug" here was that mapstructure didn't correctly guess the translation between field name and JSON key. You can give it explicit instructions in 2 ways: either create a mapstructure.DecoderConfig and set the TagName to "json" so that it uses the json tags instead of its default "mapstructure". Or you can set the mapstructure keys in the struct like so:

type PluginRuntimeDetails struct {
	Type         string `json:"type" mapstructure:"type"`
	Name         string `json:"name" mapstructure:"name"`
	OCIRuntime   string `json:"oci_runtime" mapstructure:"oci_runtime"`
	CgroupParent string `json:"cgroup_parent" mapstructure:"cgroup_parent"`
	CPU          int64  `json:"cpu" mapstructure:"cpu"`
	Memory       int64  `json:"memory" mapstructure:"memory"`
}

If you go with the latter method, the tests pass with this as the decoding step, which is probably my personal preference because the "interesting" code is more straightforward:

	if err = mapstructure.Decode(secret.Data["runtimes"], &runtimes); err != nil {
		return nil, fmt.Errorf("unable to parse runtimes")
	}

Setting DecoderConfig looks like this:

	decoderConfig := mapstructure.DecoderConfig{
		TagName: "json",
		Result:  &runtimes,
	}
	decoder, err := mapstructure.NewDecoder(&decoderConfig)
	if err != nil {
		return nil, err
	}
	if err = decoder.Decode(secret.Data["runtimes"]); err != nil {
		return nil, fmt.Errorf("unable to parse runtimes")
	}

vault/logical_system.go Outdated Show resolved Hide resolved
vault/logical_system_paths.go Outdated Show resolved Hide resolved
thyton and others added 4 commits August 31, 2023 08:31
Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
@thyton thyton merged commit 0857450 into main Aug 31, 2023
102 checks passed
@thyton thyton deleted the VAULT-18181-plugin-runtime-api branch August 31, 2023 20:37
@thyton thyton mentioned this pull request Sep 1, 2023
@thyton thyton added this to the 1.15 milestone Sep 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hashicorp-contributed-pr If the PR is HashiCorp (i.e. not-community) contributed pr/no-changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants