Skip to content

Conversation

@wk8
Copy link
Contributor

@wk8 wk8 commented Oct 8, 2019

What type of PR is this?
/kind feature

What this PR does / why we need it:

This patch adds the API generator that generates all the boilerplate files the API needs, as described in
https://github.com/kubernetes-csi/csi-proxy/tree/master/client/api#detailed-breakdown-of-generated-files

Added a few tests, though the main test will be checking that auto-generated files are in sync with the rest of the code & proto files; that will be done in automated builds in a future patch (very soon).

Which issue(s) this PR fixes:

Fixes #8

Does this PR introduce a user-facing change?:

Added a code generator to generate boilerplate files for the API

@k8s-ci-robot k8s-ci-robot added release-note Denotes a PR that will be considered when it comes time to generate release notes. kind/feature Categorizes issue or PR as related to a new feature. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Oct 8, 2019
@k8s-ci-robot k8s-ci-robot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Oct 8, 2019
Copy link
Contributor

@ddebroy ddebroy left a comment

Choose a reason for hiding this comment

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

This is looking great! Just a few comments.

CSI-proxy's API is a GRPC, versioned API.

The server exposes a number of API groups, all independent of each other. Additionally, each API group has one or more versions. Each version in each group listens for GRPC messages on a Windows named pipe of the form `\\.\\pipe\\csi-proxy-<api_group_name>-<version>` (e.g. `\\.\\pipe\\csi-proxy-filesystem-v2alpha1` or `\\.\\pipe\\csi-proxy-iscsi-v1`).
The server exposes a number of API groups, all independent of each other. Additionally, each API group has one or more versions. Each version in each group listens for GRPC messages on a Windows named pipe of the form `\\.\\pipe\\csi-proxy-<api_group_name>-<version>` (e.g. `\\.\\pipe\\csi-proxy-file_system-v2alpha1` or `\\.\\pipe\\csi-proxy-iscsi-v1`).
Copy link
Contributor

Choose a reason for hiding this comment

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

How about keeping the API groups single word as much as possible - like filesystem, volume, disk, smb and iscsi?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FileSystem seemed cleaner than Filesystem in the places where we need camel case, so that's why I changed this - but happy to change back if you prefer.

Copy link
Contributor

Choose a reason for hiding this comment

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

My preference would be filesystem.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

client/utils.go Outdated
// CsiProxyNamedPipePrefix is the prefix for the named pipes the proxy creates.
// The suffix will be the API group and version,
// e.g. "\\.\\pipe\\csi-proxy-iscsi-v1", "\\.\\pipe\\csi-proxy-filesystem-v2alpha1", etc.
// e.g. "\\.\\pipe\\csi-proxy-iscsi-v1", "\\.\\pipe\\csi-proxy-file_system-v2alpha1", etc.
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep this filesystem

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍


const (
// csiProxyRootPath is the CSI-proxy go library's root path.
csiProxyRootPath = "github.com/kubernetes-csi/csi-proxy"
Copy link
Contributor

Choose a reason for hiding this comment

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

Will it be possible to make csiProxyRootPath configurable through a parameter? Can be done in a separate PR later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, though I'm not sure what the use case would be?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking it will make name changes easier to handle in the future. Looks like k/k hardcodes this as well in their generators: https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/staging/src/k8s.io/code-generator/cmd/conversion-gen/generators/conversion.go#L439-L440. So fine for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But even then, that parameter presumably would have to be hardcoded somewhere in this repo? :)

Keeping as is for now anyway, as agreed.

tagName = "csi-proxy-gen"

// headerComment is the comment on the first line of every generated file.
headerComment = "// Code generated by csi-api-gen. DO NOT EDIT.\n\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

How about calling the utility csi-proxy-api-gen? It is not generating CSI APIs, so csi-api-gen does not sound right.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough, changing :)

// * +csi-proxy-gen=clientBasePkg:<pkg_path> to set the base output directory
// for generated client files - defaults to github.com/kubernetes-csi/csi-proxy/client/groups
tagMarker = "+"
tagName = "csi-proxy-gen"
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on comment below, I think this (as well as the comments above) should read csi-proxy-api-gen.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

unsafe "unsafe"

pb "github.com/kubernetes-csi/csi-proxy/integrationtests/apigroups/api/dummy/v1"
v1 "github.com/kubernetes-csi/csi-proxy/integrationtests/apigroups/api/dummy/v1"
Copy link
Contributor

Choose a reason for hiding this comment

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

The v1 in the alias seems unnecessary given the structure of the imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is, but that's what what gengo spits out - not sure why. Would need to go look why it does that, and change it; or just leave it as it - generated code does tend to have some quirks?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is okay for now.

return nil
}

// Convert_internal_ComputeDoubleRequest_To_v1_ComputeDoubleRequest is an autogenerated conversion function.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is Convert_internal_*Request_To_vX_*Request here mainly for completeness? I assume this conversion path will never be invoked i.e. in the Request path, Convert_vX_*Request_To_internal_*Request is what is mainly needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're absolutely right.

However, I couldn't think of a great way to tell the generic conversion generator which conversion functions you actually cared about. Best I could think of would be registering a callback with it to check if a given conversion pair is of interest - but it does add a decent amount of complexity for not much again.

If you feel strongly against having unused funcs though, can work it into code-generator.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think their presence is fine. I was wondering if they will be invoked in any scenario.

func convert_internal_ComputeDoubleResponse_To_pb_ComputeDoubleResponse(in *internal.ComputeDoubleResponse, out *pb.ComputeDoubleResponse) error {
return autoconvert_internal_ComputeDoubleResponse_To_pb_ComputeDoubleResponse(in, out)
// Convert_v1_ComputeDoubleResponse_To_internal_ComputeDoubleResponse is an autogenerated conversion function.
func Convert_v1_ComputeDoubleResponse_To_internal_ComputeDoubleResponse(in *v1.ComputeDoubleResponse, out *internal.ComputeDoubleResponse) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is Convert_vX_*Response_To_internal_*Response here mainly for completeness? I assume this conversion path will never be invoked i.e. in the Response path, Convert_internal_*Response_To_vX_*Response is what is mainly needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same answer :)

@ddebroy
Copy link
Contributor

ddebroy commented Oct 11, 2019

cc @jingxu97 @peterhornyak

@@ -0,0 +1,74 @@
package generators
Copy link
Contributor

Choose a reason for hiding this comment

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

is this file auto generated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. It contains the generator that generates api_group_generated.go files, hence the name.

@wk8
Copy link
Contributor Author

wk8 commented Oct 14, 2019

@ddebroy : amended as requested, PTAL, thanks! :)

}
}

// isError returns true iff type t is the built-in type "error"
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe spell out iff to if and only if

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍


// isError returns true iff type t is the built-in type "error"
func isError(t *types.Type) bool {
return t.Kind == types.Interface && t.Name.Name == "error" && t.Name.Package == ""
Copy link
Contributor

Choose a reason for hiding this comment

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

The t.Name.Package must be empty string? What does it mean if it is not empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It means that the type belongs to a package; that's not the case for built-in types.

Copy link
Contributor

Choose a reason for hiding this comment

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

does it make sense to name it IsBuildinError to be more specific?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Absolutely, changed, thanks :)


// buildAPIGroupDefinitionFromDocComment looks for a +csi-proxy-api-gen comment in the package's
// doc.go file, and if it finds one build the corresponding API definition.
func buildAPIGroupDefinitionFromDocComment(pkgPath string, pkg *types.Package, groups map[string]*groupDefinition) bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

the passing pkgPath is just for logging purpose in the function?

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 for logging.

}
}

klog.V(5).Infof("Found API group %q", definition.name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Here we assume we always find one api group? Is that possible that the returned commentTags are all just empty string? Is that possible that name or value is empty string? Is that valid?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If there is a csi-proxy-api-gen tag in a package's doc.go file, then it must be an API group, regardless of which additional custom options it has (or doesn't have). So yes, past the

	if len(commentTags) == 0 {
		return false
	}

check on line 160, we know we've found an API group.

continue
}

if buildAPIGroupDefinitionFromDocComment(pkgPath, pkg, groups) {
Copy link
Contributor

Choose a reason for hiding this comment

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

so it is not possible to have group definition in both subdirectories of client/api and doc.go file ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's absolutely possible. If a group definition is in the canonical client/api/ location and has a doc.go file with csi-proxy-api-gen tags, then it's processed according to the tags' options (and if there are no custom options then the tag is redundant with it being in the canonical location)

Copy link
Contributor

Choose a reason for hiding this comment

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

I am trying to check the continue logic here means if buildAPIGroupDefinitionFromDocComment returns true, the rest will be skipped?

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, it will, since the groupDefinition for it will already have been built.

func packages(context *generator.Context, arguments *args.GeneratorArgs) (pkgs generator.Packages) {
// find API group definitions
groups := findAPIGroupDefinitions(context)
klog.V(5).Infof("Found API groups: %v", groups)
Copy link
Contributor

Choose a reason for hiding this comment

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

all your log level is 5 or above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's all debugging logging.

for _, filePath := range toRemove {
filePath = path.Join(outputBase, filePath)
if err := os.RemoveAll(filePath); err != nil {
klog.Fatalf("unable to remove %q: %v", filePath, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

do we want to retry or just fail immediately?

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 what error modes could justify retrying later?

Also, the general philosophy here (and also for all of k8s' code generation, as far as I can tell) is that since this is not a service, and is just meant to be run manually (or in tests) from time to time, it's okay to fail early.

removeGeneratedFiles(group, arguments.OutputBase)
pkgs = append(pkgs, packagesForGroup(group, arguments.OutputBase)...)
}
return
Copy link
Contributor

Choose a reason for hiding this comment

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

So it just returns nothing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, it's returning pkgs

@wk8 wk8 force-pushed the wk8/generator branch 3 times, most recently from 7cd45af to c54f852 Compare October 15, 2019 19:57
// are up-to-date (i.e. consistent with the current generator).

// TestNewAPIGroup tests that bootstraping a new group works as intended.
func TestNewAPIGroup(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This test case is having a doc.go? Maybe it is not that important, just want to check is there other test cases that are interesting to us?

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 it does have one. Testing API groups from canonical locations will be done by re-generating all auto-generated files as part of all builds.

Copy link
Contributor

@ddebroy ddebroy left a comment

Choose a reason for hiding this comment

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

Can we name the cmd tool directory (cmd/csi-api-gen/) csi-proxy-api-gen as well please? Thanks for updating the name elsewhere in the code/comments.

Copy link
Contributor

@ddebroy ddebroy left a comment

Choose a reason for hiding this comment

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

Just a few nits. Otherwise looking good.

@@ -0,0 +1,11 @@
package main
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we name the cmd tool directory (cmd/csi-api-gen/ above) csi-proxy-api-gen as well please? Thanks for updating the name elsewhere in the code/comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

if err := os.MkdirAll(path.Dir(docFilePath), os.ModePerm); err != nil {
klog.Fatalf("Unable to create directory %q: %v", path.Dir(docFilePath), err)
}
contents := fmt.Sprintf("// Temporary code generated by conversion-gen. DO NOT COMMIT.\n// Should be removed automatically, but please remove manually otherwise.\n\npackage %s\n", vsn.Name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this say conversion-gen or csi-proxy-api-gen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

// validateServerCallback checks that server callbacks have the expected shape, i.e.:
// * all versioned (i.e. in the same package) parameter should be pointers
// * return values should all be pointers, except for the last one, that must be an error
// These assumptions are
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: check the comment sentence

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But they... are!

Changed, thanks :)

The API generator generates all the boilerplate files the API needs, as described in
https://github.com/kubernetes-csi/csi-proxy/tree/master/client/api#detailed-breakdown-of-generated-files

Added a few tests, though the main test will be checking that auto-generated files are in sync with the rest of the code & proto files; that will be done in automated builds in a future patch (very soon).

Signed-off-by: Jean Rouge <rougej+github@gmail.com>
Copy link
Contributor

@ddebroy ddebroy left a comment

Choose a reason for hiding this comment

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

/lgtm
/approve

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Oct 16, 2019
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: ddebroy, wk8

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

The pull request process is described here

Details 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

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Oct 16, 2019
@k8s-ci-robot k8s-ci-robot merged commit 28e6078 into kubernetes-csi:master Oct 16, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/feature Categorizes issue or PR as related to a new feature. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Write generator + ps1 script to run it

4 participants