diff --git a/boskos/cleaner/BUILD.bazel b/boskos/cleaner/BUILD.bazel index 6edb01e63c45..4875b4a6c7b3 100644 --- a/boskos/cleaner/BUILD.bazel +++ b/boskos/cleaner/BUILD.bazel @@ -34,6 +34,6 @@ go_test( "//boskos/common:go_default_library", "//boskos/mason:go_default_library", "//boskos/ranch:go_default_library", - "@io_k8s_sigs_controller_runtime//pkg/client/fake:go_default_library", + "//boskos/storage:go_default_library", ], ) diff --git a/boskos/cleaner/cleaner_test.go b/boskos/cleaner/cleaner_test.go index 88dd73b7b05f..32f13060a9d2 100644 --- a/boskos/cleaner/cleaner_test.go +++ b/boskos/cleaner/cleaner_test.go @@ -17,15 +17,13 @@ limitations under the License. package cleaner import ( - "context" "testing" "time" - fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" - "k8s.io/test-infra/boskos/common" "k8s.io/test-infra/boskos/mason" "k8s.io/test-infra/boskos/ranch" + "k8s.io/test-infra/boskos/storage" ) const ( @@ -45,7 +43,7 @@ type fakeBoskos struct { // Create a fake client func createFakeBoskos(resources []common.Resource, dlrcs []common.DynamicResourceLifeCycle) (*ranch.Storage, boskosClient, chan releasedResource) { names := make(chan releasedResource, 100) - s, _ := ranch.NewStorage(context.Background(), fakectrlruntimeclient.NewFakeClient(), "", "") + s, _ := ranch.NewStorage(storage.NewMemoryStorage(), storage.NewMemoryStorage(), "") r, _ := ranch.NewRanch("", s, testTTL) for _, lc := range dlrcs { diff --git a/boskos/cmd/boskos/BUILD.bazel b/boskos/cmd/boskos/BUILD.bazel index 25d1662bbf29..c17c6c5beb6f 100644 --- a/boskos/cmd/boskos/BUILD.bazel +++ b/boskos/cmd/boskos/BUILD.bazel @@ -32,7 +32,6 @@ go_library( "@com_github_prometheus_client_golang//prometheus:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@com_github_spf13_viper//:go_default_library", - "@io_k8s_api//core/v1:go_default_library", ], ) diff --git a/boskos/cmd/boskos/boskos.go b/boskos/cmd/boskos/boskos.go index 963fe90bc23a..377c0297fdc9 100644 --- a/boskos/cmd/boskos/boskos.go +++ b/boskos/cmd/boskos/boskos.go @@ -27,7 +27,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" "github.com/spf13/viper" - corev1 "k8s.io/api/core/v1" "k8s.io/test-infra/boskos/crds" "k8s.io/test-infra/boskos/handlers" @@ -54,7 +53,6 @@ var ( requestTTL = flag.Duration("request-ttl", defaultRequestTTL, "request TTL before losing priority in the queue") kubeClientOptions crds.KubernetesClientOptions logLevel = flag.String("log-level", "info", fmt.Sprintf("Log level is one of %v.", logrus.AllLevels)) - namespace = flag.String("namespace", corev1.NamespaceDefault, "namespace to install on") ) var ( @@ -91,12 +89,18 @@ func main() { // main server with the main mux until we're ready health := pjutil.NewHealth() - client, err := kubeClientOptions.CacheBackedClient(*namespace, &crds.ResourceObject{}, &crds.DRLCObject{}) + rc, err := kubeClientOptions.Client(crds.ResourceType) if err != nil { - logrus.WithError(err).Fatal("unable to get client") + logrus.WithError(err).Fatal("unable to create a Resource CRD client") + } + dc, err := kubeClientOptions.Client(crds.DRLCType) + if err != nil { + logrus.WithError(err).Fatal("unable to create a DynamicResourceLifeCycle CRD client") } - storage, err := ranch.NewStorage(interrupts.Context(), client, *namespace, *storagePath) + resourceStorage := crds.NewCRDStorage(rc) + dRLCStorage := crds.NewCRDStorage(dc) + storage, err := ranch.NewStorage(resourceStorage, dRLCStorage, *storagePath) if err != nil { logrus.WithError(err).Fatal("failed to create storage") } diff --git a/boskos/cmd/cleaner/BUILD.bazel b/boskos/cmd/cleaner/BUILD.bazel index 03cdc8671429..c4f684f6b5b2 100644 --- a/boskos/cmd/cleaner/BUILD.bazel +++ b/boskos/cmd/cleaner/BUILD.bazel @@ -12,7 +12,6 @@ go_library( "//boskos/crds:go_default_library", "//boskos/ranch:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", - "@io_k8s_api//core/v1:go_default_library", ], ) diff --git a/boskos/cmd/cleaner/main.go b/boskos/cmd/cleaner/main.go index 592b61a80dc6..ef581697f335 100644 --- a/boskos/cmd/cleaner/main.go +++ b/boskos/cmd/cleaner/main.go @@ -17,7 +17,6 @@ limitations under the License. package main import ( - "context" "flag" "os" "os/signal" @@ -25,7 +24,6 @@ import ( "time" "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" "k8s.io/test-infra/boskos/cleaner" "k8s.io/test-infra/boskos/client" @@ -45,7 +43,6 @@ var ( boskosURL string username string passwordFile string - namespace string cleanerCount int ) @@ -54,7 +51,6 @@ func init() { flag.StringVar(&username, "username", "", "Username used to access the Boskos server") flag.StringVar(&passwordFile, "password-file", "", "The path to password file used to access the Boskos server") flag.IntVar(&cleanerCount, "cleaner-count", defaultCleanerCount, "Number of threads running cleanup") - flag.StringVar(&namespace, "namespace", corev1.NamespaceDefault, "namespace to install on") kubeClientOptions.AddFlags(flag.CommandLine) } @@ -63,11 +59,13 @@ func main() { kubeClientOptions.Validate() logrus.SetFormatter(&logrus.JSONFormatter{}) - kubeClient, err := kubeClientOptions.Client() + dc, err := kubeClientOptions.Client(crds.DRLCType) if err != nil { - logrus.WithError(err).Fatal("failed to construct kube client") + logrus.WithError(err).Fatal("unable to create a Resource CRD client") } - st, _ := ranch.NewStorage(context.Background(), kubeClient, namespace, "") + + resStorage := crds.NewCRDStorage(dc) + st, _ := ranch.NewStorage(nil, resStorage, "") logrus.SetFormatter(&logrus.JSONFormatter{}) client, err := client.NewClient(defaultOwner, boskosURL, username, passwordFile) diff --git a/boskos/cmd/fake-mason/BUILD.bazel b/boskos/cmd/fake-mason/BUILD.bazel index 3adf4e0d6341..eb1b0f989613 100644 --- a/boskos/cmd/fake-mason/BUILD.bazel +++ b/boskos/cmd/fake-mason/BUILD.bazel @@ -13,7 +13,6 @@ go_library( "//boskos/mason:go_default_library", "//boskos/ranch:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", - "@io_k8s_api//core/v1:go_default_library", ], ) diff --git a/boskos/cmd/fake-mason/main.go b/boskos/cmd/fake-mason/main.go index 657a03170a38..f499783e1fee 100644 --- a/boskos/cmd/fake-mason/main.go +++ b/boskos/cmd/fake-mason/main.go @@ -24,14 +24,14 @@ import ( "syscall" "time" + "k8s.io/test-infra/boskos/crds" + "k8s.io/test-infra/boskos/ranch" + "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" "k8s.io/test-infra/boskos/client" "k8s.io/test-infra/boskos/common" - "k8s.io/test-infra/boskos/crds" "k8s.io/test-infra/boskos/mason" - "k8s.io/test-infra/boskos/ranch" ) const ( @@ -47,7 +47,6 @@ var ( username = flag.String("username", "", "Username used to access the Boskos server") passwordFile = flag.String("password-file", "", "The path to password file used to access the Boskos server") cleanerCount = flag.Int("cleaner-count", defaultCleanerCount, "Number of threads running cleanup") - namespace = flag.String("namespace", corev1.NamespaceDefault, "namespace to install on") kubeClientOptions crds.KubernetesClientOptions ) @@ -69,12 +68,13 @@ func main() { logrus.SetFormatter(&logrus.JSONFormatter{}) - kubeClient, err := kubeClientOptions.Client() + dc, err := kubeClientOptions.Client(crds.DRLCType) if err != nil { - logrus.WithError(err).Fatal("failed to create kubeClient") + logrus.WithError(err).Fatal("unable to create a DynamicResourceLifeCycle CRD client") } - st, _ := ranch.NewStorage(context.Background(), kubeClient, *namespace, "") + dRLCStorage := crds.NewCRDStorage(dc) + st, _ := ranch.NewStorage(nil, dRLCStorage, "") flag.Parse() logrus.SetFormatter(&logrus.JSONFormatter{}) diff --git a/boskos/common/common.go b/boskos/common/common.go index 28166f32aa33..74f21b3828e2 100644 --- a/boskos/common/common.go +++ b/boskos/common/common.go @@ -340,7 +340,7 @@ func (ud *UserData) FromMap(m UserDataMap) { func ItemToResource(i Item) (Resource, error) { res, ok := i.(Resource) if !ok { - return Resource{}, fmt.Errorf("expected item to be of type Resource, was %T", i) + return Resource{}, fmt.Errorf("cannot construct Resource from received object %v", i) } return res, nil } diff --git a/boskos/crds/BUILD.bazel b/boskos/crds/BUILD.bazel index ddc0d7a8c6f3..aea5e657cb55 100644 --- a/boskos/crds/BUILD.bazel +++ b/boskos/crds/BUILD.bazel @@ -4,28 +4,24 @@ go_library( name = "go_default_library", srcs = [ "client.go", + "crd_storage.go", "drlc_crd.go", - "register.go", "resource_crd.go", ], importpath = "k8s.io/test-infra/boskos/crds", visibility = ["//visibility:public"], deps = [ "//boskos/common:go_default_library", - "//prow/interrupts:go_default_library", - "@com_github_sirupsen_logrus//:go_default_library", + "//boskos/storage:go_default_library", "@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1beta1:go_default_library", "@io_k8s_apiextensions_apiserver//pkg/client/clientset/clientset:go_default_library", "@io_k8s_apimachinery//pkg/api/errors:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", "@io_k8s_apimachinery//pkg/runtime:go_default_library", "@io_k8s_apimachinery//pkg/runtime/schema:go_default_library", - "@io_k8s_client_go//kubernetes/scheme:go_default_library", + "@io_k8s_apimachinery//pkg/runtime/serializer:go_default_library", "@io_k8s_client_go//rest:go_default_library", "@io_k8s_client_go//tools/clientcmd:go_default_library", - "@io_k8s_sigs_controller_runtime//pkg/client:go_default_library", - "@io_k8s_sigs_controller_runtime//pkg/client/fake:go_default_library", - "@io_k8s_sigs_controller_runtime//pkg/manager:go_default_library", ], ) diff --git a/boskos/crds/client.go b/boskos/crds/client.go index e41fd444f999..452c39e72cce 100644 --- a/boskos/crds/client.go +++ b/boskos/crds/client.go @@ -17,27 +17,26 @@ limitations under the License. package crds import ( - "context" - "errors" "flag" "fmt" "os" - "time" - "github.com/sirupsen/logrus" + "k8s.io/test-infra/boskos/common" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" - fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/manager" +) - "k8s.io/test-infra/boskos/common" - "k8s.io/test-infra/prow/interrupts" +const ( + group = "boskos.k8s.io" + version = "v1" ) // KubernetesClientOptions are flag options used to create a kube client. @@ -49,6 +48,7 @@ type KubernetesClientOptions struct { // AddFlags adds kube client flags to existing FlagSet. func (o *KubernetesClientOptions) AddFlags(fs *flag.FlagSet) { + fs.StringVar(&o.namespace, "namespace", v1.NamespaceDefault, "namespace to install on") fs.StringVar(&o.kubeConfig, "kubeconfig", "", "absolute path to the kubeConfig file") fs.BoolVar(&o.inMemory, "in_memory", false, "Use in memory client instead of CRD") } @@ -64,91 +64,32 @@ func (o *KubernetesClientOptions) Validate() error { } // Client returns a ClientInterface based on the flags provided. -func (o *KubernetesClientOptions) Client() (ctrlruntimeclient.Client, error) { +func (o *KubernetesClientOptions) Client(t Type) (ClientInterface, error) { if o.inMemory { - return fakectrlruntimeclient.NewFakeClient(), nil - } - - cfg, err := o.cfg() - if err != nil { - return nil, err - } - - if err := registerResources(cfg); err != nil { - return nil, fmt.Errorf("failed to create CRDs: %v", err) + return newDummyClient(t), nil } - - return ctrlruntimeclient.New(cfg, ctrlruntimeclient.Options{}) + return o.newCRDClient(t) } -// CacheBackedClient returns a client whose Reader is cache backed. Namespace can be empty -// in which case the client will use all namespaces. -// It blocks until the cache was synced for all types passed in startCacheFor. -func (o *KubernetesClientOptions) CacheBackedClient(namespace string, startCacheFor ...runtime.Object) (ctrlruntimeclient.Client, error) { - if o.inMemory { - return fakectrlruntimeclient.NewFakeClient(), nil - } - - cfg, err := o.cfg() +// newClientFromFlags creates a CRD rest client from provided flags. +func (o *KubernetesClientOptions) newCRDClient(t Type) (*Client, error) { + config, scheme, err := createRESTConfig(o.kubeConfig, t) if err != nil { return nil, err } - if err := registerResources(cfg); err != nil { - return nil, fmt.Errorf("failed to create CRDs: %v", err) - } - - mgr, err := manager.New(cfg, manager.Options{LeaderElection: false, Namespace: namespace}) - if err != nil { - return nil, fmt.Errorf("failed to construct manager: %v", err) - } - - // Allocate an informer so our cache actually waits for these types to - // be synced. Must be done before we start the mgr, else this may block - // indefinitely if there is an issue. - for _, t := range startCacheFor { - if _, err := mgr.GetCache().GetInformer(t); err != nil { - return nil, fmt.Errorf("failed to get informer for type %T: %v", t, err) - } - } - - interrupts.Run(func(ctx context.Context) { - // Exiting like this is not nice, but the interrupts package - // doesn't allow us to stop the app. Furthermore, the behaviour - // of the reading client is undefined after the manager stops, - // so we should bail ASAP. - if err := mgr.Start(ctx.Done()); err != nil { - logrus.WithError(err).Fatal("Mgr failed.") - } - logrus.Info("Mgr finished gracefully.") - os.Exit(0) - }) - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - startSyncTime := time.Now() - if synced := mgr.GetCache().WaitForCacheSync(ctx.Done()); !synced { - return nil, errors.New("timeout waiting for cache sync") - } - logrus.WithField("sync-duration", time.Since(startSyncTime).String()).Info("Cache synced") - - return mgr.GetClient(), nil -} - -func (o *KubernetesClientOptions) cfg() (*rest.Config, error) { - var cfg *rest.Config - var err error - if o.kubeConfig == "" { - cfg, err = rest.InClusterConfig() - } else { - cfg, err = clientcmd.BuildConfigFromFlags("", o.kubeConfig) + if err = registerResource(config, t); err != nil { + return nil, err } + // creates the client + var restClient *rest.RESTClient + restClient, err = rest.RESTClientFor(config) if err != nil { - return nil, fmt.Errorf("failed to construct rest config: %v", err) + return nil, err } - - return cfg, nil + rc := Client{cl: restClient, ns: o.namespace, t: t, + codec: runtime.NewParameterCodec(scheme)} + return &rc, nil } // Type defines a Custom Resource Definition (CRD) Type. @@ -175,50 +116,233 @@ type Collection interface { GetItems() []Object } -// registerResources sends a request to create CRDs -func registerResources(config *rest.Config) error { - c, err := apiextensionsclient.NewForConfig(config) +// createRESTConfig for cluster API server, pass empty config file for in-cluster +func createRESTConfig(kubeconfig string, t Type) (config *rest.Config, types *runtime.Scheme, err error) { + if kubeconfig == "" { + config, err = rest.InClusterConfig() + } else { + config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + } + if err != nil { - return err + return } - resourceCRD := &apiextensionsv1beta1.CustomResourceDefinition{ - ObjectMeta: v1.ObjectMeta{ - Name: fmt.Sprintf("%s.%s", ResourceType.Plural, group), - }, - Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ - Group: group, - Version: version, - Scope: apiextensionsv1beta1.NamespaceScoped, - Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Singular: ResourceType.Singular, - Plural: ResourceType.Plural, - Kind: ResourceType.Kind, - ListKind: ResourceType.ListKind, - }, - }, + version := schema.GroupVersion{ + Group: group, + Version: version, } - if _, err := c.ApiextensionsV1beta1().CustomResourceDefinitions().Create(resourceCRD); err != nil && !apierrors.IsAlreadyExists(err) { + + config.GroupVersion = &version + config.APIPath = "/apis" + config.ContentType = runtime.ContentTypeJSON + + types = runtime.NewScheme() + schemeBuilder := runtime.NewSchemeBuilder( + func(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(version, t.Object, t.Collection) + v1.AddToGroupVersion(scheme, version) + return nil + }) + err = schemeBuilder.AddToScheme(types) + config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(types)} + + return +} + +// registerResource sends a request to create CRDs and waits for them to initialize +func registerResource(config *rest.Config, t Type) error { + c, err := apiextensionsclient.NewForConfig(config) + if err != nil { return err } - dlrcCRD := &apiextensionsv1beta1.CustomResourceDefinition{ + + crd := &apiextensionsv1beta1.CustomResourceDefinition{ ObjectMeta: v1.ObjectMeta{ - Name: fmt.Sprintf("%s.%s", DRLCType.Plural, group), + Name: fmt.Sprintf("%s.%s", t.Plural, group), }, Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ Group: group, Version: version, Scope: apiextensionsv1beta1.NamespaceScoped, Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Singular: DRLCType.Singular, - Plural: DRLCType.Plural, - Kind: DRLCType.Kind, - ListKind: DRLCType.ListKind, + Singular: t.Singular, + Plural: t.Plural, + Kind: t.Kind, + ListKind: t.ListKind, }, }, } - if _, err := c.ApiextensionsV1beta1().CustomResourceDefinitions().Create(dlrcCRD); err != nil && !apierrors.IsAlreadyExists(err) { + if _, err := c.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd); err != nil && !apierrors.IsAlreadyExists(err) { return err } return nil } + +// newDummyClient creates a in memory client representation for testing, such that we do not need to use a kubernetes API Server. +func newDummyClient(t Type) *dummyClient { + c := &dummyClient{ + t: t, + objects: make(map[string]Object), + } + return c +} + +// ClientInterface is used for testing. +type ClientInterface interface { + // NewObject instantiates a new object of the type supported by the client + NewObject() Object + // NewCollection instantiates a new collection of the type supported by the client + NewCollection() Collection + // Create a new object + Create(obj Object) (Object, error) + // Update an existing object, fails if object does not exist + Update(obj Object) (Object, error) + // Delete an existing object, fails if objects does not exist + Delete(name string, options *v1.DeleteOptions) error + // Get an existing object + Get(name string) (Object, error) + // LIst existing objects + List(opts v1.ListOptions) (Collection, error) +} + +// dummyClient is used for testing purposes +type dummyClient struct { + objects map[string]Object + t Type +} + +// NewObject implements ClientInterface +func (c *dummyClient) NewObject() Object { + return c.t.Object.DeepCopyObject().(Object) +} + +// NewCollection implements ClientInterface +func (c *dummyClient) NewCollection() Collection { + return c.t.Collection.DeepCopyObject().(Collection) +} + +// Create implements ClientInterface +func (c *dummyClient) Create(obj Object) (Object, error) { + c.objects[obj.GetName()] = obj + return obj, nil +} + +// Update implements ClientInterface +func (c *dummyClient) Update(obj Object) (Object, error) { + _, ok := c.objects[obj.GetName()] + if !ok { + return nil, fmt.Errorf("cannot find object %s", obj.GetName()) + } + c.objects[obj.GetName()] = obj + return obj, nil +} + +// Delete implements ClientInterface +func (c *dummyClient) Delete(name string, options *v1.DeleteOptions) error { + _, ok := c.objects[name] + if ok { + delete(c.objects, name) + return nil + } + return fmt.Errorf("%s does not exist", name) +} + +// Get implements ClientInterface +func (c *dummyClient) Get(name string) (Object, error) { + obj, ok := c.objects[name] + if ok { + return obj, nil + } + return nil, fmt.Errorf("could not find %s", name) +} + +// List implements ClientInterface +func (c *dummyClient) List(opts v1.ListOptions) (Collection, error) { + var items []Object + for _, i := range c.objects { + items = append(items, i) + } + r := c.NewCollection() + r.SetItems(items) + return r, nil +} + +// Client implements a true CRD rest client +type Client struct { + cl *rest.RESTClient + ns string + t Type + codec runtime.ParameterCodec +} + +// NewObject implements ClientInterface +func (c *Client) NewObject() Object { + return c.t.Object.DeepCopyObject().(Object) +} + +// NewCollection implements ClientInterface +func (c *Client) NewCollection() Collection { + return c.t.Collection.DeepCopyObject().(Collection) +} + +// Create implements ClientInterface +func (c *Client) Create(obj Object) (Object, error) { + result := c.NewObject() + err := c.cl.Post(). + Namespace(c.ns). + Resource(c.t.Plural). + Name(obj.GetName()). + Body(obj). + Do(). + Into(result) + return result, err +} + +// Update implements ClientInterface +func (c *Client) Update(obj Object) (Object, error) { + result := c.NewObject() + err := c.cl.Put(). + Namespace(c.ns). + Resource(c.t.Plural). + Body(obj). + Name(obj.GetName()). + Do(). + Into(result) + return result, err +} + +// Delete implements ClientInterface +func (c *Client) Delete(name string, options *v1.DeleteOptions) error { + return c.cl.Delete(). + Namespace(c.ns). + Resource(c.t.Plural). + Name(name). + Body(options). + Do(). + Error() +} + +// Get implements ClientInterface +func (c *Client) Get(name string) (Object, error) { + result := c.NewObject() + err := c.cl.Get(). + Namespace(c.ns). + Resource(c.t.Plural). + Name(name). + Do(). + Into(result) + return result, err +} + +// List implements ClientInterface +func (c *Client) List(opts v1.ListOptions) (Collection, error) { + result := c.NewCollection() + err := c.cl.Get(). + Namespace(c.ns). + Resource(c.t.Plural). + VersionedParams(&opts, c.codec). + Do(). + Into(result) + return result, err +} diff --git a/boskos/crds/crd_storage.go b/boskos/crds/crd_storage.go new file mode 100644 index 000000000000..b4cc2e7e017a --- /dev/null +++ b/boskos/crds/crd_storage.go @@ -0,0 +1,83 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crds + +import ( + "k8s.io/test-infra/boskos/common" + "k8s.io/test-infra/boskos/storage" + + "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + deleteGracePeriodSeconds = 10 +) + +type inClusterStorage struct { + client ClientInterface +} + +// NewCRDStorage creates a Custom Resource Definition persistence layer +func NewCRDStorage(client ClientInterface) storage.PersistenceLayer { + return &inClusterStorage{ + client: client, + } +} + +func (cs *inClusterStorage) Add(i common.Item) error { + o := cs.client.NewObject() + o.FromItem(i) + _, err := cs.client.Create(o) + return err +} + +func (cs *inClusterStorage) Delete(name string) error { + return cs.client.Delete(name, v1.NewDeleteOptions(deleteGracePeriodSeconds)) +} + +func (cs *inClusterStorage) Update(i common.Item) (common.Item, error) { + o, err := cs.client.Get(i.GetName()) + if err != nil { + return nil, err + } + o.FromItem(i) + updated, err := cs.client.Update(o) + if err != nil { + return nil, err + } + return updated.ToItem(), nil +} + +func (cs *inClusterStorage) Get(name string) (common.Item, error) { + o, err := cs.client.Get(name) + if err != nil { + return nil, err + } + return o.ToItem(), nil +} + +func (cs *inClusterStorage) List() ([]common.Item, error) { + col, err := cs.client.List(v1.ListOptions{}) + if err != nil { + return nil, err + } + var items []common.Item + for _, i := range col.GetItems() { + items = append(items, i.ToItem()) + } + return items, nil +} diff --git a/boskos/crds/drlc_crd.go b/boskos/crds/drlc_crd.go index 57c532980ad2..73fbb0e61dd7 100644 --- a/boskos/crds/drlc_crd.go +++ b/boskos/crds/drlc_crd.go @@ -30,14 +30,19 @@ var ( // DRLCType is the DynamicResourceLifeCycle CRD type DRLCType = Type{ Kind: reflect.TypeOf(DRLCObject{}).Name(), - ListKind: reflect.TypeOf(DRLCObjectList{}).Name(), + ListKind: reflect.TypeOf(DRLCCollection{}).Name(), Singular: "dynamicresourcelifecycle", Plural: "dynamicresourcelifecycles", Object: &DRLCObject{}, - Collection: &DRLCObjectList{}, + Collection: &DRLCCollection{}, } ) +// NewTestDRLCClient creates a fake CRD rest client for common.Resource +func NewTestDRLCClient() ClientInterface { + return newDummyClient(DRLCType) +} + // DRLCObject holds generalized configuration information about how the // resource needs to be created. // Some Resource might not have a ResourcezConfig (Example Project) @@ -57,11 +62,11 @@ type DRLCSpec struct { Needs common.ResourceNeeds `json:"needs"` } -// DRLCObjectList implements the Collections interface -type DRLCObjectList struct { +// DRLCCollection implements the Collections interface +type DRLCCollection struct { v1.TypeMeta `json:",inline"` v1.ListMeta `json:"metadata,omitempty"` - Items []DRLCObject `json:"items"` + Items []*DRLCObject `json:"items"` } // GetName implements the Object interface @@ -129,41 +134,41 @@ func (in *DRLCObject) FromItem(i common.Item) { } // GetItems implements the Collection interface -func (in *DRLCObjectList) GetItems() []Object { +func (in *DRLCCollection) GetItems() []Object { var items []Object - for idx := range in.Items { - items = append(items, &in.Items[idx]) + for _, i := range in.Items { + items = append(items, i) } return items } // SetItems implements the Collection interface -func (in *DRLCObjectList) SetItems(objects []Object) { - var items []DRLCObject +func (in *DRLCCollection) SetItems(objects []Object) { + var items []*DRLCObject for _, b := range objects { - items = append(items, *(b.(*DRLCObject))) + items = append(items, b.(*DRLCObject)) } in.Items = items } -func (in *DRLCObjectList) deepCopyInto(out *DRLCObjectList) { +func (in *DRLCCollection) deepCopyInto(out *DRLCCollection) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) out.Items = in.Items } -func (in *DRLCObjectList) deepCopy() *DRLCObjectList { +func (in *DRLCCollection) deepCopy() *DRLCCollection { if in == nil { return nil } - out := new(DRLCObjectList) + out := new(DRLCCollection) in.deepCopyInto(out) return out } // DeepCopyObject implements the runtime.Object interface -func (in *DRLCObjectList) DeepCopyObject() runtime.Object { +func (in *DRLCCollection) DeepCopyObject() runtime.Object { if c := in.deepCopy(); c != nil { return c } diff --git a/boskos/crds/register.go b/boskos/crds/register.go deleted file mode 100644 index 7fa30d30ac03..000000000000 --- a/boskos/crds/register.go +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package crds - -import ( - "fmt" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/kubernetes/scheme" -) - -func init() { - if err := AddToScheme(scheme.Scheme); err != nil { - panic(fmt.Sprintf("failed to add boskos scheme: %v", err)) - } -} - -var ( - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - AddToScheme = SchemeBuilder.AddToScheme -) - -const ( - group = "boskos.k8s.io" - version = "v1" -) - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: group, Version: version} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &ResourceObject{}, - &ResourceObjectList{}, - &DRLCObject{}, - &DRLCObjectList{}, - ) - - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/boskos/crds/resource_crd.go b/boskos/crds/resource_crd.go index d8272c237a6f..bf846f8c0de5 100644 --- a/boskos/crds/resource_crd.go +++ b/boskos/crds/resource_crd.go @@ -30,14 +30,19 @@ var ( // ResourceType is the ResourceObject CRD type ResourceType = Type{ Kind: reflect.TypeOf(ResourceObject{}).Name(), - ListKind: reflect.TypeOf(ResourceObjectList{}).Name(), + ListKind: reflect.TypeOf(ResourceCollection{}).Name(), Singular: "resource", Plural: "resources", Object: &ResourceObject{}, - Collection: &ResourceObjectList{}, + Collection: &ResourceCollection{}, } ) +// NewTestResourceClient creates a fake CRD rest client for common.Resource +func NewTestResourceClient() ClientInterface { + return newDummyClient(ResourceType) +} + // ResourceObject represents common.ResourceObject. It implements the Object interface. type ResourceObject struct { v1.TypeMeta `json:",inline"` @@ -46,11 +51,11 @@ type ResourceObject struct { Status ResourceStatus `json:"status,omitempty"` } -// ResourceObjectList is the Collection implementation -type ResourceObjectList struct { +// ResourceCollection is the Collection implementation +type ResourceCollection struct { v1.TypeMeta `json:",inline"` v1.ListMeta `json:"metadata,omitempty"` - Items []ResourceObject `json:"items"` + Items []*ResourceObject `json:"items"` } // ResourceSpec holds information that are not likely to change @@ -133,41 +138,41 @@ func (in *ResourceObject) FromItem(i common.Item) { } // GetItems implements Collection interface -func (in *ResourceObjectList) GetItems() []Object { +func (in *ResourceCollection) GetItems() []Object { var items []Object - for idx := range in.Items { - items = append(items, &in.Items[idx]) + for _, i := range in.Items { + items = append(items, i) } return items } // SetItems implements Collection interface -func (in *ResourceObjectList) SetItems(objects []Object) { - var items []ResourceObject +func (in *ResourceCollection) SetItems(objects []Object) { + var items []*ResourceObject for _, b := range objects { - items = append(items, *(b.(*ResourceObject))) + items = append(items, b.(*ResourceObject)) } in.Items = items } -func (in *ResourceObjectList) deepCopyInto(out *ResourceObjectList) { +func (in *ResourceCollection) deepCopyInto(out *ResourceCollection) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) out.Items = in.Items } -func (in *ResourceObjectList) deepCopy() *ResourceObjectList { +func (in *ResourceCollection) deepCopy() *ResourceCollection { if in == nil { return nil } - out := new(ResourceObjectList) + out := new(ResourceCollection) in.deepCopyInto(out) return out } // DeepCopyObject implements Collection interface -func (in *ResourceObjectList) DeepCopyObject() runtime.Object { +func (in *ResourceCollection) DeepCopyObject() runtime.Object { if c := in.deepCopy(); c != nil { return c } diff --git a/boskos/handlers/BUILD.bazel b/boskos/handlers/BUILD.bazel index 50c7b46d5ff5..fb82bb4ec357 100644 --- a/boskos/handlers/BUILD.bazel +++ b/boskos/handlers/BUILD.bazel @@ -23,8 +23,8 @@ go_test( deps = [ "//boskos/client:go_default_library", "//boskos/common:go_default_library", + "//boskos/crds:go_default_library", "//boskos/ranch:go_default_library", - "@io_k8s_sigs_controller_runtime//pkg/client/fake:go_default_library", ], ) diff --git a/boskos/handlers/handlers_test.go b/boskos/handlers/handlers_test.go index d557b9017df0..1d4092a7cdbc 100644 --- a/boskos/handlers/handlers_test.go +++ b/boskos/handlers/handlers_test.go @@ -25,9 +25,8 @@ import ( "testing" "time" - fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" - "k8s.io/test-infra/boskos/common" + "k8s.io/test-infra/boskos/crds" "k8s.io/test-infra/boskos/ranch" ) @@ -44,7 +43,9 @@ var ( ) func MakeTestRanch(resources []common.Resource) *ranch.Ranch { - s := ranch.NewTestingStorage(fakectrlruntimeclient.NewFakeClient(), "test", func() time.Time { return fakeNow }) + resourceClient := crds.NewTestResourceClient() + dRLCClient := crds.NewTestDRLCClient() + s := ranch.NewTestingStorage(crds.NewCRDStorage(resourceClient), crds.NewCRDStorage(dRLCClient), func() time.Time { return fakeNow }) for _, r := range resources { s.AddResource(r) } diff --git a/boskos/mason/BUILD.bazel b/boskos/mason/BUILD.bazel index 26bd41242d46..e00dc17ce485 100644 --- a/boskos/mason/BUILD.bazel +++ b/boskos/mason/BUILD.bazel @@ -23,7 +23,7 @@ go_test( deps = [ "//boskos/common:go_default_library", "//boskos/ranch:go_default_library", - "@io_k8s_sigs_controller_runtime//pkg/client/fake:go_default_library", + "//boskos/storage:go_default_library", ], ) diff --git a/boskos/mason/mason_test.go b/boskos/mason/mason_test.go index 8f366e80e9cd..ad20c6bae9fa 100644 --- a/boskos/mason/mason_test.go +++ b/boskos/mason/mason_test.go @@ -17,16 +17,16 @@ limitations under the License. package mason import ( - "context" "fmt" "strings" "testing" "time" - fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" + "context" "k8s.io/test-infra/boskos/common" "k8s.io/test-infra/boskos/ranch" + "k8s.io/test-infra/boskos/storage" ) var ( @@ -98,7 +98,7 @@ func (fc *fakeConfig) Construct(ctx context.Context, res common.Resource, typeTo func createFakeBoskos(tc testConfig) (*ranch.Storage, *Client, chan releasedResource) { names := make(chan releasedResource, 100) configNames := map[string]bool{} - s, _ := ranch.NewStorage(context.Background(), fakectrlruntimeclient.NewFakeClient(), "test", "") + s, _ := ranch.NewStorage(storage.NewMemoryStorage(), storage.NewMemoryStorage(), "") r, _ := ranch.NewRanch("", s, testTTL) for rtype, c := range tc { diff --git a/boskos/ranch/BUILD.bazel b/boskos/ranch/BUILD.bazel index ce6d25088a09..574987c95de0 100644 --- a/boskos/ranch/BUILD.bazel +++ b/boskos/ranch/BUILD.bazel @@ -15,8 +15,7 @@ go_test( embed = [":go_default_library"], deps = [ "//boskos/common:go_default_library", - "@com_github_sirupsen_logrus//:go_default_library", - "@io_k8s_sigs_controller_runtime//pkg/client/fake:go_default_library", + "//boskos/crds:go_default_library", ], ) @@ -30,13 +29,10 @@ go_library( importpath = "k8s.io/test-infra/boskos/ranch", deps = [ "//boskos/common:go_default_library", - "//boskos/crds:go_default_library", + "//boskos/storage:go_default_library", "@com_github_hashicorp_go_multierror//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", - "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", - "@io_k8s_apimachinery//pkg/types:go_default_library", "@io_k8s_apimachinery//pkg/util/sets:go_default_library", - "@io_k8s_sigs_controller_runtime//pkg/client:go_default_library", ], ) diff --git a/boskos/ranch/ranch.go b/boskos/ranch/ranch.go index 3d2f440c16d7..1d23acd6bb8e 100644 --- a/boskos/ranch/ranch.go +++ b/boskos/ranch/ranch.go @@ -59,7 +59,7 @@ type ResourceTypeNotFound struct { } func (r ResourceTypeNotFound) Error() string { - return fmt.Sprintf("resource type %q does not exist", r.rType) + return fmt.Sprintf("resource type %s does not exist", r.rType) } // OwnerNotMatch will be returned if request owner does not match current owner for target resource. @@ -178,7 +178,7 @@ func (r *Ranch) Acquire(rType, state, dest, owner, requestID string) (*common.Re if new { logger.Debug("Checking for associated dynamic resource type...") lifeCycle, err := r.Storage.GetDynamicResourceLifeCycle(rType) - // Assuming error means no associated dynamic resource. + // Assuming error means no associated dynamic resource if err == nil { if typeCount < lifeCycle.MaxCount { logger.Debug("Adding new dynamic resources...") @@ -188,8 +188,6 @@ func (r *Ranch) Acquire(rType, state, dest, owner, requestID string) (*common.Re } logger.Infof("Added dynamic resource %s of type %s", res.Name, res.Type) } - } else { - logrus.WithError(err).Debug("Failed listing DRLC") } } diff --git a/boskos/ranch/ranch_test.go b/boskos/ranch/ranch_test.go index 82a49aa4cdf5..3d0ed5fbad23 100644 --- a/boskos/ranch/ranch_test.go +++ b/boskos/ranch/ranch_test.go @@ -17,7 +17,6 @@ limitations under the License. package ranch import ( - "context" "fmt" "reflect" "sort" @@ -25,17 +24,10 @@ import ( "testing" "time" - "github.com/sirupsen/logrus" - fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" - "k8s.io/test-infra/boskos/common" + "k8s.io/test-infra/boskos/crds" ) -// Make debugging a bit easier -func init() { - logrus.SetLevel(logrus.DebugLevel) -} - var ( startTime = fakeTime(time.Now()) fakeNow = fakeTime(startTime.Add(time.Second)) @@ -61,7 +53,9 @@ func fakeTime(t time.Time) time.Time { } func MakeTestRanch(resources []common.Resource, dResources []common.DynamicResourceLifeCycle) *Ranch { - s, _ := NewStorage(context.Background(), fakectrlruntimeclient.NewFakeClient(), "test", "") + rs := crds.NewCRDStorage(crds.NewTestResourceClient()) + lfs := crds.NewCRDStorage(crds.NewTestDRLCClient()) + s, _ := NewStorage(rs, lfs, "") s.now = func() time.Time { return fakeNow } @@ -233,9 +227,7 @@ func TestAcquirePriority(t *testing.T) { if _, err := r.Acquire(res.Type, res.State, common.Dirty, owner, "request_id_1"); err == nil { t.Errorf("should fail as there are not resource available") } - if err := r.Storage.AddResource(res); err != nil { - t.Fatalf("failed to add resource: %v", err) - } + r.Storage.AddResource(res) // Attempting to acquire this resource without priority if _, err := r.Acquire(res.Type, res.State, common.Dirty, owner, ""); err == nil { t.Errorf("should fail as there is only resource, and it is prioritizes to request_id_1") @@ -246,7 +238,7 @@ func TestAcquirePriority(t *testing.T) { } // Attempting with the first request if _, err := r.Acquire(res.Type, res.State, common.Dirty, owner, "request_id_1"); err != nil { - t.Fatalf("should succeed since the request priority should match its rank in the queue. got %v", err) + t.Errorf("should succeed since the request priority should match its rank in the queue. got %v", err) } r.Release(res.Name, common.Free, "tester") // Attempting with the first request @@ -309,9 +301,9 @@ func TestAcquireOnDemand(t *testing.T) { t.Errorf("should fail since there is not resource yet") } if resources, err := c.Storage.GetResources(); err != nil { - t.Fatal(err) + t.Error(err) } else if len(resources) != 1 { - t.Fatal("A resource should have been created") + t.Errorf("A resource should have been created") } // Attempting to create another resource if _, err := c.Acquire(rType, common.Free, common.Busy, owner, requestID1); err == nil { diff --git a/boskos/ranch/storage.go b/boskos/ranch/storage.go index 1d3373e93bc8..85e842b6a258 100644 --- a/boskos/ranch/storage.go +++ b/boskos/ranch/storage.go @@ -17,9 +17,7 @@ limitations under the License. package ranch import ( - "context" "encoding/json" - "fmt" "io/ioutil" "os" "reflect" @@ -29,20 +27,14 @@ import ( "github.com/hashicorp/go-multierror" "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" - "k8s.io/test-infra/boskos/common" - "k8s.io/test-infra/boskos/crds" + "k8s.io/test-infra/boskos/storage" ) // Storage is used to decouple ranch functionality with the resource persistence layer type Storage struct { - ctx context.Context - client ctrlruntimeclient.Client - namespace string - resourcesLock sync.RWMutex + resources, dynamicResourceLifeCycles storage.PersistenceLayer + resourcesLock sync.RWMutex // For testing now func() time.Time @@ -50,23 +42,22 @@ type Storage struct { } // NewTestingStorage is used only for testing. -func NewTestingStorage(client ctrlruntimeclient.Client, namespace string, updateTime func() time.Time) *Storage { +func NewTestingStorage(res, lf storage.PersistenceLayer, updateTime func() time.Time) *Storage { return &Storage{ - ctx: context.Background(), - client: client, - namespace: namespace, - now: updateTime, + resources: res, + dynamicResourceLifeCycles: lf, + now: updateTime, } } // NewStorage instantiates a new Storage with a PersistenceLayer implementation // If storage string is not empty, it will read resource data from the file -func NewStorage(ctx context.Context, client ctrlruntimeclient.Client, namespace, storage string) (*Storage, error) { +func NewStorage(res, lf storage.PersistenceLayer, storage string) (*Storage, error) { s := &Storage{ - client: client, - namespace: namespace, - now: func() time.Time { return time.Now() }, - generateName: common.GenerateDynamicResourceName, + resources: res, + dynamicResourceLifeCycles: lf, + now: func() time.Time { return time.Now() }, + generateName: common.GenerateDynamicResourceName, } if storage != "" { @@ -96,134 +87,115 @@ func NewStorage(ctx context.Context, client ctrlruntimeclient.Client, namespace, // AddResource adds a new resource func (s *Storage) AddResource(resource common.Resource) error { - o := &crds.ResourceObject{} - o.FromItem(resource) - o.Namespace = s.namespace - return s.client.Create(s.ctx, o) + return s.resources.Add(resource) } // DeleteResource deletes a resource if it exists, errors otherwise func (s *Storage) DeleteResource(name string) error { - o := &crds.ResourceObject{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: s.namespace, - }, - } - return s.client.Delete(s.ctx, o) + return s.resources.Delete(name) } // UpdateResource updates a resource if it exists, errors otherwise func (s *Storage) UpdateResource(resource common.Resource) (common.Resource, error) { resource.LastUpdate = s.now() - - o := &crds.ResourceObject{} - name := types.NamespacedName{Namespace: s.namespace, Name: resource.GetName()} - if err := s.client.Get(s.ctx, name, o); err != nil { - return common.Resource{}, fmt.Errorf("failed to get resource %s before patching it: %v", resource.GetName(), err) + i, err := s.resources.Update(resource) + if err != nil { + return common.Resource{}, err } - - o.FromItem(resource) - if err := s.client.Update(s.ctx, o); err != nil { - return common.Resource{}, fmt.Errorf("failed to update resources %s after patching it: %v", resource.GetName(), err) + var res common.Resource + res, err = common.ItemToResource(i) + if err != nil { + return common.Resource{}, err } - - return common.ItemToResource(o.ToItem()) + return res, nil } // GetResource gets an existing resource, errors otherwise func (s *Storage) GetResource(name string) (common.Resource, error) { - o := &crds.ResourceObject{} - nn := types.NamespacedName{Namespace: s.namespace, Name: name} - if err := s.client.Get(s.ctx, nn, o); err != nil { - return common.Resource{}, fmt.Errorf("failed to get resource %s: %v", name, err) + i, err := s.resources.Get(name) + if err != nil { + return common.Resource{}, err } - - return common.ItemToResource(o.ToItem()) + var res common.Resource + res, err = common.ItemToResource(i) + if err != nil { + return common.Resource{}, err + } + return res, nil } // GetResources list all resources func (s *Storage) GetResources() ([]common.Resource, error) { - resourceList := &crds.ResourceObjectList{} - if err := s.client.List(s.ctx, resourceList, ctrlruntimeclient.InNamespace(s.namespace)); err != nil { - return nil, fmt.Errorf("failed to list resources; %v", err) - } - var resources []common.Resource - for _, resource := range resourceList.Items { - res, err := common.ItemToResource(resource.ToItem()) + items, err := s.resources.List() + if err != nil { + return resources, err + } + for _, i := range items { + var res common.Resource + res, err = common.ItemToResource(i) if err != nil { - return nil, fmt.Errorf("failed to convert item %s to resource: %v", resource.Name, err) + return nil, err } resources = append(resources, res) } - sort.Stable(common.ResourceByUpdateTime(resources)) return resources, nil } // AddDynamicResourceLifeCycle adds a new dynamic resource life cycle func (s *Storage) AddDynamicResourceLifeCycle(resource common.DynamicResourceLifeCycle) error { - o := &crds.DRLCObject{} - o.FromItem(resource) - o.Namespace = s.namespace - return s.client.Create(s.ctx, o) + return s.dynamicResourceLifeCycles.Add(resource) } // DeleteDynamicResourceLifeCycle deletes a dynamic resource life cycle if it exists, errors otherwise func (s *Storage) DeleteDynamicResourceLifeCycle(name string) error { - o := &crds.DRLCObject{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: s.namespace, - }, - } - return s.client.Delete(s.ctx, o) + return s.dynamicResourceLifeCycles.Delete(name) } // UpdateDynamicResourceLifeCycle updates a dynamic resource life cycle. if it exists, errors otherwise func (s *Storage) UpdateDynamicResourceLifeCycle(resource common.DynamicResourceLifeCycle) (common.DynamicResourceLifeCycle, error) { - dlrc := &crds.DRLCObject{} - name := types.NamespacedName{Namespace: s.namespace, Name: resource.GetName()} - if err := s.client.Get(s.ctx, name, dlrc); err != nil { - return common.DynamicResourceLifeCycle{}, fmt.Errorf("failed to get dlrc %s before patching it: %v", resource.GetName(), err) + i, err := s.dynamicResourceLifeCycles.Update(resource) + if err != nil { + return common.DynamicResourceLifeCycle{}, err } - - dlrc.FromItem(resource) - if err := s.client.Update(s.ctx, dlrc); err != nil { - return common.DynamicResourceLifeCycle{}, fmt.Errorf("failed to update dlrc %s after patching it: %v", resource.GetName(), err) + var res common.DynamicResourceLifeCycle + res, err = common.ItemToDynamicResourceLifeCycle(i) + if err != nil { + return common.DynamicResourceLifeCycle{}, err } - - return common.ItemToDynamicResourceLifeCycle(dlrc.ToItem()) + return res, nil } // GetDynamicResourceLifeCycle gets an existing dynamic resource life cycle, errors otherwise func (s *Storage) GetDynamicResourceLifeCycle(name string) (common.DynamicResourceLifeCycle, error) { - dlrc := &crds.DRLCObject{} - nn := types.NamespacedName{Namespace: s.namespace, Name: name} - if err := s.client.Get(s.ctx, nn, dlrc); err != nil { - return common.DynamicResourceLifeCycle{}, fmt.Errorf("failed to get dlrc %s: %q", name, err) + i, err := s.dynamicResourceLifeCycles.Get(name) + if err != nil { + return common.DynamicResourceLifeCycle{}, err } - - return common.ItemToDynamicResourceLifeCycle(dlrc.ToItem()) + var res common.DynamicResourceLifeCycle + res, err = common.ItemToDynamicResourceLifeCycle(i) + if err != nil { + return common.DynamicResourceLifeCycle{}, err + } + return res, nil } // GetDynamicResourceLifeCycles list all dynamic resource life cycle func (s *Storage) GetDynamicResourceLifeCycles() ([]common.DynamicResourceLifeCycle, error) { - dlrcList := &crds.DRLCObjectList{} - if err := s.client.List(s.ctx, dlrcList, ctrlruntimeclient.InNamespace(s.namespace)); err != nil { - return nil, fmt.Errorf("failed to list dlrcs: %v", err) - } - var resources []common.DynamicResourceLifeCycle - for _, dlrc := range dlrcList.Items { - res, err := common.ItemToDynamicResourceLifeCycle(dlrc.ToItem()) + items, err := s.dynamicResourceLifeCycles.List() + if err != nil { + return resources, err + } + for _, i := range items { + var res common.DynamicResourceLifeCycle + res, err = common.ItemToDynamicResourceLifeCycle(i) if err != nil { - return nil, fmt.Errorf("failed to convert dlrc %s: %v", dlrc.GetName(), err) + return nil, err } resources = append(resources, res) } - return resources, nil } diff --git a/boskos/storage/BUILD.bazel b/boskos/storage/BUILD.bazel index d90e63f6a4fd..bc424c30560c 100644 --- a/boskos/storage/BUILD.bazel +++ b/boskos/storage/BUILD.bazel @@ -12,7 +12,10 @@ go_test( name = "go_default_test", srcs = ["storage_test.go"], embed = [":go_default_library"], - deps = ["//boskos/common:go_default_library"], + deps = [ + "//boskos/common:go_default_library", + "//boskos/crds:go_default_library", + ], ) filegroup( diff --git a/boskos/storage/storage_test.go b/boskos/storage/storage_test.go index d0c9d3128ca8..4400aefd660a 100644 --- a/boskos/storage/storage_test.go +++ b/boskos/storage/storage_test.go @@ -25,12 +25,14 @@ import ( "testing" "k8s.io/test-infra/boskos/common" + "k8s.io/test-infra/boskos/crds" "k8s.io/test-infra/boskos/storage" ) func createStorages() []storage.PersistenceLayer { return []storage.PersistenceLayer{ + crds.NewCRDStorage(crds.NewTestResourceClient()), storage.NewMemoryStorage(), } }