1/* 2Copyright 2018 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package fake 18 19import ( 20 "context" 21 "fmt" 22 "strings" 23 24 "k8s.io/apimachinery/pkg/api/meta" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/labels" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/runtime/serializer" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/apimachinery/pkg/watch" 32 "k8s.io/client-go/metadata" 33 "k8s.io/client-go/testing" 34) 35 36// MetadataClient assists in creating fake objects for use when testing, since metadata.Getter 37// does not expose create 38type MetadataClient interface { 39 metadata.Getter 40 CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) 41 UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) 42} 43 44// NewSimpleMetadataClient creates a new client that will use the provided scheme and respond with the 45// provided objects when requests are made. It will track actions made to the client which can be checked 46// with GetActions(). 47func NewSimpleMetadataClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeMetadataClient { 48 gvkFakeList := schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "List"} 49 if !scheme.Recognizes(gvkFakeList) { 50 // In order to use List with this client, you have to have the v1.List registered in your scheme, since this is a test 51 // type we modify the input scheme 52 scheme.AddKnownTypeWithName(gvkFakeList, &metav1.List{}) 53 } 54 55 codecs := serializer.NewCodecFactory(scheme) 56 o := testing.NewObjectTracker(scheme, codecs.UniversalDeserializer()) 57 for _, obj := range objects { 58 if err := o.Add(obj); err != nil { 59 panic(err) 60 } 61 } 62 63 cs := &FakeMetadataClient{scheme: scheme} 64 cs.AddReactor("*", "*", testing.ObjectReaction(o)) 65 cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 66 gvr := action.GetResource() 67 ns := action.GetNamespace() 68 watch, err := o.Watch(gvr, ns) 69 if err != nil { 70 return false, nil, err 71 } 72 return true, watch, nil 73 }) 74 75 return cs 76} 77 78// FakeMetadataClient implements clientset.Interface. Meant to be embedded into a 79// struct to get a default implementation. This makes faking out just the method 80// you want to test easier. 81type FakeMetadataClient struct { 82 testing.Fake 83 scheme *runtime.Scheme 84} 85 86type metadataResourceClient struct { 87 client *FakeMetadataClient 88 namespace string 89 resource schema.GroupVersionResource 90} 91 92var _ metadata.Interface = &FakeMetadataClient{} 93 94// Resource returns an interface for accessing the provided resource. 95func (c *FakeMetadataClient) Resource(resource schema.GroupVersionResource) metadata.Getter { 96 return &metadataResourceClient{client: c, resource: resource} 97} 98 99// Namespace returns an interface for accessing the current resource in the specified 100// namespace. 101func (c *metadataResourceClient) Namespace(ns string) metadata.ResourceInterface { 102 ret := *c 103 ret.namespace = ns 104 return &ret 105} 106 107// CreateFake records the object creation and processes it via the reactor. 108func (c *metadataResourceClient) CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { 109 var uncastRet runtime.Object 110 var err error 111 switch { 112 case len(c.namespace) == 0 && len(subresources) == 0: 113 uncastRet, err = c.client.Fake. 114 Invokes(testing.NewRootCreateAction(c.resource, obj), obj) 115 116 case len(c.namespace) == 0 && len(subresources) > 0: 117 var accessor metav1.Object // avoid shadowing err 118 accessor, err = meta.Accessor(obj) 119 if err != nil { 120 return nil, err 121 } 122 name := accessor.GetName() 123 uncastRet, err = c.client.Fake. 124 Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj) 125 126 case len(c.namespace) > 0 && len(subresources) == 0: 127 uncastRet, err = c.client.Fake. 128 Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj) 129 130 case len(c.namespace) > 0 && len(subresources) > 0: 131 var accessor metav1.Object // avoid shadowing err 132 accessor, err = meta.Accessor(obj) 133 if err != nil { 134 return nil, err 135 } 136 name := accessor.GetName() 137 uncastRet, err = c.client.Fake. 138 Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj) 139 140 } 141 142 if err != nil { 143 return nil, err 144 } 145 if uncastRet == nil { 146 return nil, err 147 } 148 ret, ok := uncastRet.(*metav1.PartialObjectMetadata) 149 if !ok { 150 return nil, fmt.Errorf("unexpected return value type %T", uncastRet) 151 } 152 return ret, err 153} 154 155// UpdateFake records the object update and processes it via the reactor. 156func (c *metadataResourceClient) UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { 157 var uncastRet runtime.Object 158 var err error 159 switch { 160 case len(c.namespace) == 0 && len(subresources) == 0: 161 uncastRet, err = c.client.Fake. 162 Invokes(testing.NewRootUpdateAction(c.resource, obj), obj) 163 164 case len(c.namespace) == 0 && len(subresources) > 0: 165 uncastRet, err = c.client.Fake. 166 Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj) 167 168 case len(c.namespace) > 0 && len(subresources) == 0: 169 uncastRet, err = c.client.Fake. 170 Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj) 171 172 case len(c.namespace) > 0 && len(subresources) > 0: 173 uncastRet, err = c.client.Fake. 174 Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj) 175 176 } 177 178 if err != nil { 179 return nil, err 180 } 181 if uncastRet == nil { 182 return nil, err 183 } 184 ret, ok := uncastRet.(*metav1.PartialObjectMetadata) 185 if !ok { 186 return nil, fmt.Errorf("unexpected return value type %T", uncastRet) 187 } 188 return ret, err 189} 190 191// UpdateStatus records the object status update and processes it via the reactor. 192func (c *metadataResourceClient) UpdateStatus(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions) (*metav1.PartialObjectMetadata, error) { 193 var uncastRet runtime.Object 194 var err error 195 switch { 196 case len(c.namespace) == 0: 197 uncastRet, err = c.client.Fake. 198 Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj) 199 200 case len(c.namespace) > 0: 201 uncastRet, err = c.client.Fake. 202 Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj) 203 204 } 205 206 if err != nil { 207 return nil, err 208 } 209 if uncastRet == nil { 210 return nil, err 211 } 212 ret, ok := uncastRet.(*metav1.PartialObjectMetadata) 213 if !ok { 214 return nil, fmt.Errorf("unexpected return value type %T", uncastRet) 215 } 216 return ret, err 217} 218 219// Delete records the object deletion and processes it via the reactor. 220func (c *metadataResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error { 221 var err error 222 switch { 223 case len(c.namespace) == 0 && len(subresources) == 0: 224 _, err = c.client.Fake. 225 Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "metadata delete fail"}) 226 227 case len(c.namespace) == 0 && len(subresources) > 0: 228 _, err = c.client.Fake. 229 Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata delete fail"}) 230 231 case len(c.namespace) > 0 && len(subresources) == 0: 232 _, err = c.client.Fake. 233 Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata delete fail"}) 234 235 case len(c.namespace) > 0 && len(subresources) > 0: 236 _, err = c.client.Fake. 237 Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "metadata delete fail"}) 238 } 239 240 return err 241} 242 243// DeleteCollection records the object collection deletion and processes it via the reactor. 244func (c *metadataResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error { 245 var err error 246 switch { 247 case len(c.namespace) == 0: 248 action := testing.NewRootDeleteCollectionAction(c.resource, listOptions) 249 _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"}) 250 251 case len(c.namespace) > 0: 252 action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions) 253 _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"}) 254 255 } 256 257 return err 258} 259 260// Get records the object retrieval and processes it via the reactor. 261func (c *metadataResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { 262 var uncastRet runtime.Object 263 var err error 264 switch { 265 case len(c.namespace) == 0 && len(subresources) == 0: 266 uncastRet, err = c.client.Fake. 267 Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "metadata get fail"}) 268 269 case len(c.namespace) == 0 && len(subresources) > 0: 270 uncastRet, err = c.client.Fake. 271 Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"}) 272 273 case len(c.namespace) > 0 && len(subresources) == 0: 274 uncastRet, err = c.client.Fake. 275 Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata get fail"}) 276 277 case len(c.namespace) > 0 && len(subresources) > 0: 278 uncastRet, err = c.client.Fake. 279 Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"}) 280 } 281 282 if err != nil { 283 return nil, err 284 } 285 if uncastRet == nil { 286 return nil, err 287 } 288 ret, ok := uncastRet.(*metav1.PartialObjectMetadata) 289 if !ok { 290 return nil, fmt.Errorf("unexpected return value type %T", uncastRet) 291 } 292 return ret, err 293} 294 295// List records the object deletion and processes it via the reactor. 296func (c *metadataResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) { 297 var obj runtime.Object 298 var err error 299 switch { 300 case len(c.namespace) == 0: 301 obj, err = c.client.Fake. 302 Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, opts), &metav1.Status{Status: "metadata list fail"}) 303 304 case len(c.namespace) > 0: 305 obj, err = c.client.Fake. 306 Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, c.namespace, opts), &metav1.Status{Status: "metadata list fail"}) 307 308 } 309 310 if obj == nil { 311 return nil, err 312 } 313 314 label, _, _ := testing.ExtractFromListOptions(opts) 315 if label == nil { 316 label = labels.Everything() 317 } 318 319 inputList, ok := obj.(*metav1.List) 320 if !ok { 321 return nil, fmt.Errorf("incoming object is incorrect type %T", obj) 322 } 323 324 list := &metav1.PartialObjectMetadataList{ 325 ListMeta: inputList.ListMeta, 326 } 327 for i := range inputList.Items { 328 item, ok := inputList.Items[i].Object.(*metav1.PartialObjectMetadata) 329 if !ok { 330 return nil, fmt.Errorf("item %d in list %T is %T", i, inputList, inputList.Items[i].Object) 331 } 332 metadata, err := meta.Accessor(item) 333 if err != nil { 334 return nil, err 335 } 336 if label.Matches(labels.Set(metadata.GetLabels())) { 337 list.Items = append(list.Items, *item) 338 } 339 } 340 return list, nil 341} 342 343func (c *metadataResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 344 switch { 345 case len(c.namespace) == 0: 346 return c.client.Fake. 347 InvokesWatch(testing.NewRootWatchAction(c.resource, opts)) 348 349 case len(c.namespace) > 0: 350 return c.client.Fake. 351 InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts)) 352 353 } 354 355 panic("math broke") 356} 357 358// Patch records the object patch and processes it via the reactor. 359func (c *metadataResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { 360 var uncastRet runtime.Object 361 var err error 362 switch { 363 case len(c.namespace) == 0 && len(subresources) == 0: 364 uncastRet, err = c.client.Fake. 365 Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "metadata patch fail"}) 366 367 case len(c.namespace) == 0 && len(subresources) > 0: 368 uncastRet, err = c.client.Fake. 369 Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"}) 370 371 case len(c.namespace) > 0 && len(subresources) == 0: 372 uncastRet, err = c.client.Fake. 373 Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "metadata patch fail"}) 374 375 case len(c.namespace) > 0 && len(subresources) > 0: 376 uncastRet, err = c.client.Fake. 377 Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"}) 378 379 } 380 381 if err != nil { 382 return nil, err 383 } 384 if uncastRet == nil { 385 return nil, err 386 } 387 ret, ok := uncastRet.(*metav1.PartialObjectMetadata) 388 if !ok { 389 return nil, fmt.Errorf("unexpected return value type %T", uncastRet) 390 } 391 return ret, err 392} 393