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

[Federation][join-01] Implement kubefed join command. #35493

Merged

Conversation

madhusudancs
Copy link
Contributor

@madhusudancs madhusudancs commented Oct 25, 2016

Supersedes PR #35155.

Please review only the last commit here. This is based on PR #35492 which will be reviewed independently.

I will add a release note separately for this entire feature, so please don't worry too much about the release note here in the PR.

Design Doc: PR #34484

cc @kubernetes/sig-cluster-federation @quinton-hoole @mwielgus


This change is Reviewable

@madhusudancs madhusudancs added area/cluster-federation release-note-none Denotes a PR that doesn't merit a release note. labels Oct 25, 2016
@madhusudancs madhusudancs added this to the v1.5 milestone Oct 25, 2016
@k8s-github-robot k8s-github-robot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Oct 25, 2016
join_long = templates.LongDesc(`
Join a cluster to a federation.

Current context is assumed to be a federation endpoint.
Copy link
Contributor

Choose a reason for hiding this comment

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

"federation endpoint" is not obvious in what it means. How about "is assumed to be for federation control plane" or "is assumed to be for federation apiserver"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

// `PathOptions()`) and a mechanism to talk to the federation host
// cluster.
type JoinFederationConfig interface {
// PathOptions provides filesystem based kubeconfig access.
Copy link
Contributor

Choose a reason for hiding this comment

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

kubeconfig access to what?

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 kubeconfig-access. In other words access to kubeconfig. I thought about "filesystem based access to kubeconfig", but that doesn't sound right in this context, so wrote it this way.

Copy link
Contributor

Choose a reason for hiding this comment

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

:)

HostFactory(host, kubeconfigPath string) cmdutil.Factory
}

// joinFederationConfig implements JoinFederationConfig interface.
Copy link
Contributor

Choose a reason for hiding this comment

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

Add var _ JoinFederationConfig = &joinFederationConfig{} to verify and document that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
}

func (r *joinFederationConfig) PathOptions() *clientcmd.PathOptions {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: why use r for joinFederationConfig? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed from r to j.

You might have already guessed the reason behind r. This receiver struct did not start its life with this name. It started as registerFederationConfig :-)

po.LoadingRules.ExplicitPath = kubeconfig
clientConfig, err := po.GetStartingConfig()
if err != nil {
return err
Copy link
Contributor

Choose a reason for hiding this comment

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

Its generally recommended to add some context while returning error message. Like
return fmt.Errorf("error %s in getting starting config for path options: %v", po, err)

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 would love to do that where the error type isn't important. But it is in this case. Adding that context coerces the error type to some unknown internal type of fmt (it is actually whatever returned by golang/pkg/errors.New()). That's a type precision loss and that means cmdutil.CheckErr(err) in the caller has no idea about the type of the error and can't do the right thing. So I think we should just return the received error without coercing it to a string.


// Build the secret object with the minified and flattened
// kubeconfig content.
secret := &api.Secret{
Copy link
Contributor

Choose a reason for hiding this comment

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

Use versioned clientset and create versioned secret object here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cmdutil.Factory doesn't support external types yet, so it is not possible. I started writing code that way, but then had to switch back. I know how hand-tied it feels ;-)

// host:port part of the endpoint URL.
u, err := urlParse(cluster.Server)
if err != nil {
return nil, fmt.Errorf("failed to parse cluster endpoint %q for cluster %q", cluster.Server, name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Also include err in this error message

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching that!

Done.

}
serverAddress := cluster.Server
if u.scheme == "" {
// Use "https" as the default scheme if the port isn't "80". It
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 its better to leave this. Users will have unexpected successes/failures by changing the port number. As a user I would have no clue what is happening if I hit this.
PS: By leave this, I mean choose a default. Dont change it as per port number.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you still decide to keep it, I will urge you to add 8080, since thats the port used by our local cluster up script :)
But still think removing this is best

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, that's a fair argument.

Dropping the port number based inference and just plain defaulting to https. Also, removing the complex parsing logic and dramatically simplifying this portion.

Copy link
Contributor

Choose a reason for hiding this comment

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

Great, thx! :)

hostport = segs[1]
}

remsegs := strings.SplitN(hostport, "/", 2)
Copy link
Contributor

Choose a reason for hiding this comment

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

whats rem in remsegs? :)

Copy link
Contributor

Choose a reason for hiding this comment

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

remaining? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. Sorry, remsegs wasn't obvious. Changed it to remainingSegs for clarity.

if len(segs) == 2 {
scheme = segs[0]
hostport = segs[1]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You could call net/url.Parse now once you have figured out the scheme?

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 thought about that, but it is uglier than this. You need to check if the scheme was already in the string prefix. And only if not you need to stick it in front of the string and then parse it. This is easier/less uglier.

@nikhiljindal
Copy link
Contributor

High level comments:

  • Will be good to add glog.V(2).Info() type comments which can help a lot in debugging. Right now there are none :)
  • Its generally recommended to add some context while returning an err, which makes it easier to track the error was from which caller.
  • Re code structure. As per kubectl code convention, this code should be in federation/pkg/kubefed/cmd/join.go. Was choosing a different dir structure intentional?

Copy link
Contributor Author

@madhusudancs madhusudancs left a comment

Choose a reason for hiding this comment

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

@nikhiljindal addressed the comments. PTAL.

join_long = templates.LongDesc(`
Join a cluster to a federation.

Current context is assumed to be a federation endpoint.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

// `PathOptions()`) and a mechanism to talk to the federation host
// cluster.
type JoinFederationConfig interface {
// PathOptions provides filesystem based kubeconfig access.
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 kubeconfig-access. In other words access to kubeconfig. I thought about "filesystem based access to kubeconfig", but that doesn't sound right in this context, so wrote it this way.

HostFactory(host, kubeconfigPath string) cmdutil.Factory
}

// joinFederationConfig implements JoinFederationConfig interface.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
}

func (r *joinFederationConfig) PathOptions() *clientcmd.PathOptions {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed from r to j.

You might have already guessed the reason behind r. This receiver struct did not start its life with this name. It started as registerFederationConfig :-)

po.LoadingRules.ExplicitPath = kubeconfig
clientConfig, err := po.GetStartingConfig()
if err != nil {
return err
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 would love to do that where the error type isn't important. But it is in this case. Adding that context coerces the error type to some unknown internal type of fmt (it is actually whatever returned by golang/pkg/errors.New()). That's a type precision loss and that means cmdutil.CheckErr(err) in the caller has no idea about the type of the error and can't do the right thing. So I think we should just return the received error without coercing it to a string.


// Build the secret object with the minified and flattened
// kubeconfig content.
secret := &api.Secret{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

cmdutil.Factory doesn't support external types yet, so it is not possible. I started writing code that way, but then had to switch back. I know how hand-tied it feels ;-)

// host:port part of the endpoint URL.
u, err := urlParse(cluster.Server)
if err != nil {
return nil, fmt.Errorf("failed to parse cluster endpoint %q for cluster %q", cluster.Server, name)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching that!

Done.

if len(segs) == 2 {
scheme = segs[0]
hostport = segs[1]
}
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 thought about that, but it is uglier than this. You need to check if the scheme was already in the string prefix. And only if not you need to stick it in front of the string and then parse it. This is easier/less uglier.

hostport = segs[1]
}

remsegs := strings.SplitN(hostport, "/", 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.

Yeah. Sorry, remsegs wasn't obvious. Changed it to remainingSegs for clarity.

}
serverAddress := cluster.Server
if u.scheme == "" {
// Use "https" as the default scheme if the port isn't "80". It
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, that's a fair argument.

Dropping the port number based inference and just plain defaulting to https. Also, removing the complex parsing logic and dramatically simplifying this portion.

@k8s-github-robot k8s-github-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Oct 27, 2016
@madhusudancs
Copy link
Contributor Author

@nikhiljindal PTAL.

  • Will be good to add glog.V(2).Info() type comments which can help a lot in debugging. Right now there are none :)

I have added log statements where I think is appropriate.

  • Its generally recommended to add some context while returning an err, which makes it easier to track the error was from which caller.

Explained inline why this shouldn't be done in this case. But also pasting here.

I would love to do that where the error type isn't important. But it is in this case. Adding that context coerces the error type to some unknown internal type of fmt (it is actually whatever returned by golang/pkg/errors.New()). That's a type precision loss and that means cmdutil.CheckErr(err) in the caller has no idea about the type of the error and can't do the right thing. So I think we should just return the received error without coercing it to a string.

  • Re code structure. As per kubectl code convention, this code should be in federation/pkg/kubefed/cmd/join.go. Was choosing a different dir structure intentional?

Yeah. I am trying not to blindly copy the structure unless there is a good reason/explanation. kubectl has another level in the hierarchy (.../cmd/...) because there are things outside cmd such as generators etc. We don't have that today. And this is just a package. The actual binary is built from entry point federation/cmd which already has cmd to indicate that this is a command. There is no reason to have it here again. Also, kubectl's package needs to be reorganized anyway. Things are all over the place there.

@k8s-github-robot k8s-github-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Oct 27, 2016
@k8s-ci-robot
Copy link
Contributor

Jenkins GKE smoke e2e failed for commit a542b337d1435f67db7c3ee8a1f24ed62ca5d219. Full PR test history.

The magic incantation to run this job again is @k8s-bot cvm gke e2e test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@nikhiljindal
Copy link
Contributor

Only remaining comment is regarding ensuring that secret name is valid.
All else looks good.

@madhusudancs
Copy link
Contributor Author

@nikhiljindal after much deliberation, I am leaning towards not doing an automatic conversion at all. It might surprise users. And we have to explain how we do this conversion. And that conversion might break in several cases: len(context) > 256 for 2 clusters that when truncated aren't unique, some characters we miss/fail to convert and all the edge cases that come with it.

Instead we should just provide a way to customize these values via flags, so that users have more control over how these names are created if they don't confer to RFC1123 subdomain spec and have a way to reason about it.

I will add open an issue for it and implement it in a follow up PR. What do you think?

@k8s-github-robot k8s-github-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Oct 30, 2016
@k8s-ci-robot
Copy link
Contributor

Jenkins GCE Node e2e failed for commit ca59772. Full PR test history.

The magic incantation to run this job again is @k8s-bot node e2e test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@nikhiljindal
Copy link
Contributor

Yes Adding those flags in a separate PR is fine.
This LGTM now.
Feel free to add the label once you have got the tests passing.

@k8s-github-robot k8s-github-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Nov 1, 2016
@k8s-ci-robot
Copy link
Contributor

Jenkins verification failed for commit c862ffbf3e169ac4dc1e029ff72a77c853f56de8. Full PR test history.

The magic incantation to run this job again is @k8s-bot verify test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@k8s-ci-robot
Copy link
Contributor

Jenkins unit/integration failed for commit 215a629. Full PR test history.

The magic incantation to run this job again is @k8s-bot unit test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@madhusudancs
Copy link
Contributor Author

@k8s-bot unit test this

@madhusudancs
Copy link
Contributor Author

@k8s-bot verify test this

Also, add unit tests for `kubefed join`.
@madhusudancs
Copy link
Contributor Author

PR #35492 was merged. Adding the LGTM label.

@madhusudancs madhusudancs added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 1, 2016
@k8s-github-robot k8s-github-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Nov 1, 2016
@k8s-github-robot
Copy link

@k8s-bot test this [submit-queue is verifying that this PR is safe to merge]

@k8s-github-robot
Copy link

Automatic merge from submit-queue

@k8s-github-robot k8s-github-robot merged commit df8db65 into kubernetes:master Nov 2, 2016
k8s-github-robot pushed a commit that referenced this pull request Nov 2, 2016
Automatic merge from submit-queue

[Federation][unjoin-00] Implement `kubefed unjoin` command.

Please review only the last commit here. This is based on PR #35493 which will be reviewed independently.

I will add a release note separately for this entire feature, so please don't worry too much about the release note here in the PR.

Design Doc: PR #34484

cc @kubernetes/sig-cluster-federation @quinton-hoole @nikhiljindal
@madhusudancs madhusudancs deleted the federation-kubefed-01 branch November 2, 2016 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note-none Denotes a PR that doesn't merit a release note. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants