Skip to content
This repository was archived by the owner on Dec 3, 2024. It is now read-only.

Adding bucketrequest controller (WIP) #7

Merged

Conversation

brahmaroutu
Copy link
Contributor

This code adds basic bucket request Add functionality.
A controller, pkg util to support unit and functional testing.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Dec 4, 2020
@k8s-ci-robot k8s-ci-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Dec 4, 2020
@brahmaroutu brahmaroutu force-pushed the controller_code branch 3 times, most recently from c5ed674 to e6dd28a Compare December 4, 2020 19:39
@brahmaroutu
Copy link
Contributor Author

@wlan0 @jeffvance @rrati Please review.
/test pull-container-object-storage-interface-controller-build

@brahmaroutu
Copy link
Contributor Author

/test pull-container-object-storage-interface-controller-build

2 similar comments
@brahmaroutu
Copy link
Contributor Author

/test pull-container-object-storage-interface-controller-build

@brahmaroutu
Copy link
Contributor Author

/test pull-container-object-storage-interface-controller-build

@brahmaroutu brahmaroutu force-pushed the controller_code branch 3 times, most recently from 4f8836e to 730de26 Compare December 4, 2020 22:57
@brahmaroutu brahmaroutu changed the title Adding bucketrequest controller Adding bucketrequest controller (WIP) Dec 4, 2020
@brahmaroutu brahmaroutu force-pushed the controller_code branch 3 times, most recently from c6079f5 to b9cf8f7 Compare December 5, 2020 03:31
b.bucketClient = bc
}

// Add creates a bucket in response to a buckerrequest

Choose a reason for hiding this comment

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

typo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

return "InvalidBucketClass", util.ErrStopProvision
}

glog.Info(logOperation("created", "bucket %q provisioned", bucket))

Choose a reason for hiding this comment

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

why is success declared here? The provisioner hasn't even had a chance to see the new bucket instance yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Success is that the bucket is created but status is not available until provisioner acts on it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good Question. @brahmaroutu

Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't Created give the false impression that the bucket is created in the backend?

Choose a reason for hiding this comment

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

Success is that the bucket is created but status is not available until provisioner acts on it.

But the log msg states that the bucket was provisioned and this is not the case.

// Returns nil error only when the bucket was provisioned (in which case it also returns "Finished"),
// a normal error when the bucket was not provisioned and provisioning should be retried (requeue the bucketRequest),
// or the special errStopProvision when provisioning was impossible and no further attempts to provision should be tried.
func (b *bucketRequestListener) provisionBucketRequestOperation(ctx context.Context, bucketRequest *v1alpha1.BucketRequest) (string, error) {
Copy link

@jeffvance jeffvance Dec 5, 2020

Choose a reason for hiding this comment

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

did you consider the func returning (bool, error) like we see for many of the wait funcs? Where the bool is true if the controller should not retry (sort of like done)

@brahmaroutu
Copy link
Contributor Author

@brahmaroutu
Copy link
Contributor Author

/test pull-container-object-storage-interface-controller-build

@brahmaroutu
Copy link
Contributor Author

/test pull-container-object-storage-interface-controller-unit

Copy link
Contributor

@NicolasT NicolasT left a comment

Choose a reason for hiding this comment

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

First batch of comments.

Comment on lines 62 to 75
var cancel context.CancelFunc

ctx, cancel = context.WithCancel(context.Background())
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGSEGV)

go func() {
s := <-sigs
cancel()
panic(fmt.Sprintf("%s %s", s.String(), "Signal received. Exiting"))
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do this in init instead of main?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved the code.

"github.com/golang/glog"
)

var ctx context.Context
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for this global I believe, see below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

SilenceErrors: true,
SilenceUsage: true,
RunE: func(c *cobra.Command, args []string) error {
return run(args)
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use Commands Context() and pass it to run instead: https://godoc.org/github.com/spf13/cobra#Command.Context

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Using cmd context now.

}

func main() {
if err := cmd.Execute(); err != nil {
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 move the context setup out of init (where IMHO it doesn't belong) into main, you can use Commands ExecuteContext (https://godoc.org/github.com/spf13/cobra#Command.ExecuteContext)

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 run(args []string) error {
ctrl, err := bucketcontroller.NewDefaultObjectStorageController("controller-manager", "leader-lock", 40)
if err != nil {
glog.Error(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Both log and glog seem to be used throughout this 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.

Fixed, using only blog

func run(args []string) error {
ctrl, err := bucketcontroller.NewDefaultObjectStorageController("controller-manager", "leader-lock", 40)
if err != nil {
glog.Error(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Either log the error and handle it, or don't log and return it (then, expect the upper layer to log it if appropriate).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Returning error


ctx, cancel = context.WithCancel(context.Background())
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGSEGV)
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 really need/want to override the default handler for SIGSEGV? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

agree no need to catch SIGSEGV. @wlan WDYT?

go func() {
s := <-sigs
cancel()
panic(fmt.Sprintf("%s %s", s.String(), "Signal received. Exiting"))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why should this panic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed panic, I think we just fall off the go routine. Should be fine as context is cancelled.

Comment on lines +29 to +34
func (b *bucketRequestListener) InitializeKubeClient(k kubeclientset.Interface) {
b.kubeClient = k
}

func (b *bucketRequestListener) InitializeBucketClient(bc bucketclientset.Interface) {
b.bucketClient = bc
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It appears, on first sight, a bucketRequestListener can't reasonably be used unless its kubeClient and bucketClient are set up. As such, instead of having these Initialize* methods, how about requiring a kubeclientset.Interface and bucketclientset.Interface to be passed to the 'constructor' (NewListener)? Less API, less possibility of 'doing something wrong'.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some of the object require the client and some may not need. Controller listener interface is built generically. https://github.com/kubernetes-sigs/container-object-storage-interface-api/blob/43539346a903848b3f17aa0f4d41cd827d0070e1/controller/interfaces.go#L16

Copy link
Contributor

Choose a reason for hiding this comment

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

Remains an odd API which can easily be mis-used. All of a sudden a consumer needs to know what any method call requires to be set in order to 'sufficiently properly' initialize an instance.

// - the bucketRequest is already there if previous status was ProvisioningInBackground.
// - the bucketRequest is not there if if previous status was "Finished".
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

What if err != nil and status != "Finished"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reworked this code to return and process just error.

glog.V(1).Infof("add called for bucket %s", obj.Name)
bucketRequest := obj
status, err := b.provisionBucketRequestOperation(ctx, bucketRequest)
if err == nil || status == "Finished" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Finished should likely be a const somewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Code reworked as explained above.

Name: bucketRequest.Name,
Namespace: bucketRequest.Namespace,
UID: bucketRequest.ObjectMeta.UID}
bucket.Spec.AllowedNamespaces = util.CopyStrings(bucketClass.AllowedNamespaces) //could use k8s util/slice
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of these could ... comments, either do so (in final version of the code), or remove the comment 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the comments.

}
if err == nil || apierrs.IsAlreadyExists(err) {
glog.V(5).Infof("Bucket %s saved", bucket.Name)
return "exists", nil
Copy link
Contributor

Choose a reason for hiding this comment

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

All these should likely be consts, or even a custom type and an enumeration of all possible values (also consts). Not plain string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reworked the code.

return client, kubeClient, ctrl
}

func GetBuckets(ctx context.Context, client bucketclientset.Interface, numExpected int) *types.BucketList {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is likely only used in tests? Could use a comment at least to explain what the function does, how it behaves.

return false
}

func GetBucketAccesses(ctx context.Context, client bucketclientset.Interface, numExpected int) *types.BucketAccessList {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above.

return false
}

func DeleteObjects(ctx context.Context, client bucketclientset.Interface, objs ...interface{}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd keep some more specific defers in the test code rather than a catch-all like this. Seems a bit error-prone.

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 believe this way code is less verbose in the test and test is more readable. I agree user need to remember all objects to delete whether he choses to call this multiple time with multiple defer calls or at once.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's 'magic'. Go code should be explicit and simple, no 'magic'. But fair enough, matter of taste so no real objection.

}

func getCRDClient() (apiextensions.CustomResourceDefinitionInterface, error) {
config, err := func() (*rest.Config, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

See before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Documented that this is used by unit testing only.

return crdClientset.CustomResourceDefinitions(), err
}

func RegisterCRDs(ctx context.Context, client apiextensions.CustomResourceDefinitionInterface) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd argue these should be set up/managed in a test environment/cluster by the test automation, not by the test(s) themselves.

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 agree, will be reworked once we have a proper setup mechanism.

@wlan0
Copy link
Contributor

wlan0 commented Dec 8, 2020

I have minor nits only. We can take this in and address minor nits in subsequent PRs

@wlan0
Copy link
Contributor

wlan0 commented Dec 8, 2020

/lgtm

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

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: brahmaroutu, wlan0

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

@k8s-ci-robot k8s-ci-robot merged commit c330fbd into kubernetes-retired:master Dec 8, 2020
Comment on lines +66 to +75
var cancel context.CancelFunc

_, cancel = context.WithCancel(cmd.Context())
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

go func() {
<-sigs
cancel()
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

All of this comes after we ran cmd.Execute, so it seems unused whatsoever?

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 you are saying that this code should go before cmd.Execute then we will have to create the context like we did before.

Copy link
Contributor

Choose a reason for hiding this comment

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

We need to create it, but it shouldn't be a global, can remain within main 😃 Something like the following (from memory):

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()  // Just in case

    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        <-sigs
        cancel()
    }()

    if err := cmd.ExecuteContext(ctx); err != nil {
        glog.Fatal(err.Error())
    }
}


var cancel context.CancelFunc

_, cancel = context.WithCancel(cmd.Context())
Copy link
Contributor

Choose a reason for hiding this comment

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

cmd would not 'have' a Context here since we never set it. beforehand.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By default cmd gets context.Background() right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, though that's a bit of a 'fallback' in cobra. I think being explicit here (also see snippet above) is preferable.

@wlan0 wlan0 linked an issue Dec 8, 2020 that may be closed by this pull request
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
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. lgtm "Looks good to me", indicates that a PR is ready to be merged. 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.

BucketRequest Add
5 participants