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

Ipalloc newKO #123

Merged
merged 11 commits into from
May 2, 2023
Merged

Ipalloc newKO #123

merged 11 commits into from
May 2, 2023

Conversation

henderiw
Copy link
Contributor

added ipalloc with new parser interface.

Copy link
Contributor

@kispaljr kispaljr left a comment

Choose a reason for hiding this comment

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

I propose not to create an interface for each of our K8s resources with 3 methods for each of its fields, and keep the interface and the K8s API go struct in sync later, but use generic methods instead to read/write data into the kpt package.
This means that the implementation of business logic in our kpt functions will use the go structs of those K8s resources that we have to maintain anyway. All validation logic, setter functions, etc. should be added to those structs as opposed to the interfaces.

Here is a proof-of-concept implementation for this proposal:
https://github.com/nokia/nephio-nephio/tree/setspec/krm-functions/setspec-test

Notes on the implementation:

  • the repo above contains a kpt function implementation that showcases how both the current SDK and my proposal is used in a kpt function implementation
  • in this file: https://github.com/nokia/nephio-nephio/blob/setspec/krm-functions/setspec-test/testdata/test1/_expected.yaml
    you can find the output of the function: the resource named "currentsdk" is manipulated with the current SDK, and the other using my proposal. (You can check if they really generated the results in _expected.yaml by actually running the go test)
  • as you can see this PoC implementation proposal is almost equivalent in terms of keeping the comments with the current SDK
  • both versions will keep all comments in Release 1 supported scenarios
  • the implementation of SetSpec() is a temporary fix, the proper place to implement this is in the underlying kpt function SDK that we use, more precisely here: https://github.com/GoogleContainerTools/kpt-functions-sdk/blob/e8e9cb3c3ae2a19c22f52701d1542cf24e541df0/go/fn/internal/map.go#L138
    at the line that reads: // TODO: Copy comments? :))
    Unfortunately different CLA (+ my company's policy) prevents me to contribute there
  • naturally SetSpec() should be generalized and used for Status, as well (edited)

parser.Parser[*ipamv1alpha1.IPAllocation]

SetSpec(*ipamv1alpha1.IPAllocationSpec) error
// GetPrefixKind returns the prefixKind from the spec
Copy link
Contributor

Choose a reason for hiding this comment

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

I propose to remove all GetXXX() methods from the interface because of the following reasons:
This functionality is provided by the kpt-fn go SDK that we build on (github.com/GoogleContainerTools/kpt-functions-sdk/go/fn), not just for specific types, but in a generic manner to any type (let it be ipamv1alpha1.IPAllocation, or DataNetworkSpec, or any Kubernetes resource or subresource go types.
So, instead of adding these methods into this interface, we can just do this:

var ipAllocStruct ipamv1alpha1.IPAllocation
err = ipAllocObj.As(&ipAllocStruct)

and then just use the fields of ipAllocStruct

// if an error occurs or the attribute is not present an empty string is returned
GetAllocatedGateway() string
// SetPrefixKind sets the prefixKind in the spec
SetPrefixKind(ipamv1alpha1.PrefixKind) error
Copy link
Contributor

@kispaljr kispaljr Apr 17, 2023

Choose a reason for hiding this comment

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

I propose to remove all SetXXX() and DeleteXXX() methods that sets one of the fields of the Spec, and replace it with a generic function that works for any Kubernetes Resource type that has a spec field. This would update the whole Spec at once, preserving as much comments as possible (all if nothing was changed). The advantage of this would be that we don't have to reimplement this functionality for each field of each type again, in these interfaces like IPAllocation.

Simply craft a new ipamv1alpha1.IPAllocationSpec struct by optionally getting the current spec field values by the method described above (KubeObject.As()), and then updating the fields of the go struct, and then just use the new SetSpec() generic function on the corresponding fn.KubeObject, (or just use the SetSpec() method of this IPAllocation interface)

var ipAllocObj fn.KubebObject  = ...
var ipAllocSpec ipamv1alpha1.IPAllocationSpec
// set the fields of the spec to the desired state
ipAllocSpec.Index = 0
// ...
err := SetSpec(ipAllocObj, &ipAllocSpec)

Copy link
Contributor

@kispaljr kispaljr Apr 17, 2023

Choose a reason for hiding this comment

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

The easiest implementation of SetSpec() and SetStatus() would be to send a Pull Request to the underlying SDK that fixes this TODO and copies the comments recursively :)

func (o *MapVariant) setYAMLNode(key string, node *yaml.Node) {
	children := o.node.Content
	if len(children)%2 != 0 {
		log.Fatalf("unexpected number of children for map %d", len(children))
	}

	for i := 0; i < len(children); i += 2 {
		keyNode := children[i]

		k, ok := asString(keyNode)
		if ok && k == key {
			// TODO: Copy comments?
			oldNode := children[i+1]
			children[i+1] = node
			children[i+1].FootComment = oldNode.FootComment
			children[i+1].HeadComment = oldNode.HeadComment
			children[i+1].LineComment = oldNode.LineComment
			return
		}
	}

	o.node.Content = append(o.node.Content, buildStringNode(key), node)
}

// SetCreatePrefix sets the create prefix in the spec
SetCreatePrefix(bool) error
// SetAllocatedPrefix sets the allocated prefix in the status
SetAllocatedPrefix(string) error
Copy link
Contributor

@kispaljr kispaljr Apr 17, 2023

Choose a reason for hiding this comment

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

I propose to remove all SetXXX() and DeleteXXX() methods that sets one of the fields of the Status, and replace it with a generic function that works for any Kubernetes Resource that has a status field. This would update the whole status at once, preserving as much comments as possible (all if nothing was changed). The advantage of this would be that we don't have to reimplement this functionality for each field of each type again in these interfaces like IPAllocation.

Simply craft a new ipamv1alpha1.IPAllocationStatus struct by optionally getting the current status field values by the method described above (KubeObject.As()), and then updating the fields of the go struct, and then just use the new SetStatus() generic function on the corresponding fn.KubeObject:

var ipAllocObj fn.KubebObject = ...
var ipAllocStatus ipamv1alpha1.IPAllocationStatus
// set the fields of the spec to the desired state
ipAllocStatus.Gateway = "..."

err := SetStatus(ipAllocObj, &ipAllocStatus)

// NewFromYaml creates a new parser interface
// It expects raw byte slice as input representing the serialized yaml file
func NewFromYAML(b []byte) (IPAllocation, error) {
p, err := parser.NewFromYaml[*ipamv1alpha1.IPAllocation](b)
Copy link
Contributor

Choose a reason for hiding this comment

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

The github.com/GoogleContainerTools/kpt-functions-sdk/go/fn SDK that we use parses all YAML files before calling our "main" function (actually called Run()). It wraps the parsed YAML nodes into a hierarchy of KubeObject and SubObject instances. In order to avoid duplicate parsing, parsing from a YAML file (byte slice) should always be replaced by converting from a KubeObject, e.g. in this case by using the NewFromKubeObject() method of this interface.

@kispaljr kispaljr mentioned this pull request Apr 18, 2023
"fmt"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"github.com/example.com/foo/pkg/parser"
Copy link
Contributor

Choose a reason for hiding this comment

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

The pull request doesn't build on my machine due to this example.com import.
Should it be replaced with something like:
github.com/nephio-project/nephio/krm-functions/lib/parser ?


"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"github.com/example.com/foo/pkg/parser"
ipamv1alpha1 "github.com/nokia/k8s-ipam/apis/alloc/ipam/v1alpha1"
Copy link
Contributor

Choose a reason for hiding this comment

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

The latest tag on github.com/nokia/k8s-ipam is v.0.0.3, and that version doesn't contain the apis/alloc/ipam/v1alpha1 path (but the main branch does).
That confuses go mod tidy, since for go the latest module is v0.0.3, and not the main branch.
Could you add a new tag to the k8s-ipam repo to make it easier to import?

@gvbalaji
Copy link

Please see my comments on #121 which is base for others. I beleive if we take that path implementation of these individual libraries will be simpler.

@kispaljr
Copy link
Contributor

Just wanted to point out that this PR contains files from #121 and #122 .
This may be fine, I am not sure what our policy is in this scenario.

@henderiw henderiw requested a review from gvbalaji April 21, 2023 18:58
@johnbelamaric
Copy link
Member

Just wanted to point out that this PR contains files from #121 and #122 . This may be fine, I am not sure what our policy is in this scenario.

We should rebase in these cases (rebase is preferable to merge commits).

Copy link
Member

@johnbelamaric johnbelamaric left a comment

Choose a reason for hiding this comment

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

So, I am more aligned with Istvan on getting rid of the getters and setters. The idea would be that instead of getters and setters, if you want the convenience of using these in strongly typed Go structs, you unmarshal it (GetGoStruct), then you call set SetFromGoStruct, which sets the values on the underlying KubeObject, rather than just marshalling it.

I think this would work to preserve comments. The usage would be something like:

ifcWrapper := interfacev1alpha1.NewFromYaml(b)
ifc := ifcWrapper.GetGoStruct() // or maybe the more traditional pass in a pointer
ifc.CNIType = "foo"
ifcWrapper.SetFromGoStruct(ifc)

So - we have ONE getter that returns the Go struct, ONE setter that accepts the Go struct. The underlying KubeObject is updated based on the contents of the Go struct.

This is very close to that; all that is missing is the SetFromGoStruct (and to reinstate GetGoStruct).

But I am not going to push, this may just be bike shedding at this point. It's more important to get it merged soon.

@henderiw
Copy link
Contributor Author

We want to go to the gostruct approach but we need to validate that approach if it works with all lists, maps, etc w/o loosing comments when you insert, etc. So this is indeed our final target. We decided to do this in the interim, so we can start testing the fns and in parallel we will go to the go struct approach.

@henderiw henderiw reopened this Apr 28, 2023
@henderiw henderiw changed the title Ipalloc newparser Ipalloc newKO Apr 28, 2023
@henderiw
Copy link
Contributor Author

This aligns the ip allocation with the new KubeObjectExt

Copy link

@gvbalaji gvbalaji left a comment

Choose a reason for hiding this comment

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

thanks @henderiw .
/approve

@kispaljr
Copy link
Contributor

kispaljr commented Apr 28, 2023

If I understand correctly, the main reason to have this wrapper at all, is the lack of static type checking in the interface{} type parameter of SetSpec and SetStatus. I agree that this is a valid concern.
However I suggest to use the power of Go generics to fix this, as opposed to implementing it one-by-one for each type (avoiding this is the main reason for having generics).
Here s my proposal to add static type checking: #141

@kispaljr
Copy link
Contributor

kispaljr commented Apr 28, 2023

I somehow missed @johnbelamaric 's comment, so far, sorry

I think this would work to preserve comments. The usage would be something like:

ifcWrapper := interfacev1alpha1.NewFromYaml(b)
ifc := ifcWrapper.GetGoStruct() // or maybe the more traditional pass in a pointer
ifc.CNIType = "foo"
ifcWrapper.SetFromGoStruct(ifc)

So - we have ONE getter that returns the Go struct, ONE setter that accepts the Go struct. The underlying KubeObject is updated based on the contents of the Go struct.

I very much like the idea of one SetFromGoStruct() as opposed to a separate SetSpec/SetStatus. Here is my PR for that: #142 . It can live together with #141 (types-safe SetSpec & SetStatus), or it can completely replace the separate setters.

@ganchandrasekaran
Copy link
Contributor

/lgtm

@henderiw
Copy link
Contributor Author

henderiw commented May 2, 2023

The differences between the approaches are very small.
Here is my breakdown:

KubeObjectExt: expects the developer to know generics and add the right type in the generic field of the go struct. More error prune, but checked in the library.
Explicit typed interface. needs explicit code although could be generated. Easier for people to grasp.
So it depends on the target for these. A deep go developer prefers KubeObjectExt, the less experienced dev prefers Explicit typed interfaces. How I personally look at this is I look at the consumer of this and given the SDK targets less experienced people. As such this is why we created the explicit methods.

Personally I prefer KubeObjectExt, but the barrier for the consumers is higher.
My 2 cents.

Example Here.

Explicit
alloc, err := vlanlibv1alpha1.NewFromKubeObject(vlanalloc)
if err != nil {
return nil, err
}
KubeObjectExt
alloc, err := ko.NewFromKubeObject*vlanv1alpha1.VLANAllocation
if err != nil {
return nil, err
}

@nephio-prow nephio-prow bot removed the lgtm label May 2, 2023
@henderiw
Copy link
Contributor Author

henderiw commented May 2, 2023

/approve

@nephio-prow
Copy link
Contributor

nephio-prow bot commented May 2, 2023

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: gvbalaji, henderiw

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

@nephio-prow nephio-prow bot added the approved label May 2, 2023
@ganchandrasekaran
Copy link
Contributor

/lgtm

@nephio-prow nephio-prow bot added the lgtm label May 2, 2023
@nephio-prow nephio-prow bot merged commit a598037 into nephio-project:main May 2, 2023
nephio-prow bot pushed a commit that referenced this pull request May 4, 2023
… all/any fields at once (#142)

Inspired by [this
comment](#123 (review))
from @johnbelamaric : this proposal adds a setter for the whole
KubeObjectExt that allows to set all/any fields at once
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.

None yet

5 participants