0
# CRUD Operations
1
2
Full in-memory Kubernetes API implementation supporting all standard CRUD operations, resource watching, and advanced features like patches and finalizers.
3
4
## Capabilities
5
6
### KubernetesCrudDispatcher
7
8
Main CRUD dispatcher providing full in-memory Kubernetes API functionality.
9
10
```java { .api }
11
/**
12
* CRUD-mode dispatcher providing full in-memory Kubernetes API
13
* Stores resources in memory and handles all standard Kubernetes operations
14
*/
15
public class KubernetesCrudDispatcher extends CrudDispatcher
16
implements KubernetesCrudPersistence, CustomResourceAware {
17
18
/**
19
* Default constructor with empty custom resource list
20
*/
21
public KubernetesCrudDispatcher();
22
23
/**
24
* Constructor with initial custom resource definitions
25
* @param crdContexts List of custom resource definition contexts
26
*/
27
public KubernetesCrudDispatcher(List<CustomResourceDefinitionContext> crdContexts);
28
}
29
```
30
31
### Create Operations
32
33
Handle HTTP POST requests for resource creation with proper validation and storage.
34
35
```java { .api }
36
/**
37
* Handle resource creation requests
38
* Validates resources and stores them with generated resource versions
39
* @param request RecordedRequest containing resource data
40
* @return MockResponse with created resource or error
41
*/
42
public MockResponse handleCreate(RecordedRequest request);
43
```
44
45
**Supported Features:**
46
- Resource validation and schema checking
47
- Automatic metadata generation (resource version, creation timestamp)
48
- Namespace validation and defaulting
49
- Name generation for resources without names
50
- Conflict detection for duplicate resources
51
- Custom resource creation support
52
53
**Usage Examples:**
54
55
```java
56
@EnableKubernetesMockClient(crud = true)
57
class CreateOperationsTest {
58
KubernetesClient client;
59
60
@Test
61
void testPodCreation() {
62
// Create a pod
63
Pod pod = new PodBuilder()
64
.withNewMetadata()
65
.withName("test-pod")
66
.withNamespace("default")
67
.endMetadata()
68
.withNewSpec()
69
.addNewContainer()
70
.withName("app")
71
.withImage("nginx")
72
.endContainer()
73
.endSpec()
74
.build();
75
76
Pod created = client.pods().inNamespace("default").resource(pod).create();
77
78
assertNotNull(created.getMetadata().getResourceVersion());
79
assertNotNull(created.getMetadata().getCreationTimestamp());
80
assertEquals("test-pod", created.getMetadata().getName());
81
}
82
83
@Test
84
void testGeneratedNameCreation() {
85
// Create pod with generateName
86
Pod pod = new PodBuilder()
87
.withNewMetadata()
88
.withGenerateName("test-pod-")
89
.withNamespace("default")
90
.endMetadata()
91
.build();
92
93
Pod created = client.pods().inNamespace("default").resource(pod).create();
94
95
assertTrue(created.getMetadata().getName().startsWith("test-pod-"));
96
}
97
}
98
```
99
100
### Read Operations
101
102
Handle HTTP GET requests for retrieving individual resources and resource lists.
103
104
```java { .api }
105
/**
106
* Handle resource retrieval requests
107
* Supports both individual resource GET and list operations
108
* @param path Request path identifying the resource(s)
109
* @return MockResponse with resource data or 404 if not found
110
*/
111
public MockResponse handleGet(String path);
112
```
113
114
**Supported Features:**
115
- Individual resource retrieval by name
116
- Resource list operations with label and field selectors
117
- Namespace-scoped and cluster-scoped resources
118
- API discovery endpoints (e.g., `/api/v1`, `/apis/apps/v1`)
119
- Resource metadata endpoints
120
- Pagination support with continue tokens
121
122
**Usage Examples:**
123
124
```java
125
@Test
126
void testResourceRetrieval() {
127
// Create a resource first
128
Pod pod = client.pods().inNamespace("default")
129
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
130
.create();
131
132
// Retrieve by name
133
Pod retrieved = client.pods().inNamespace("default").withName("test").get();
134
assertEquals("test", retrieved.getMetadata().getName());
135
136
// List all pods
137
PodList list = client.pods().inNamespace("default").list();
138
assertEquals(1, list.getItems().size());
139
140
// List with label selector
141
client.pods().inNamespace("default").withLabel("app", "web").list();
142
}
143
144
@Test
145
void testApiDiscovery() {
146
// API discovery works automatically
147
APIResourceList v1Resources = client.getKubernetesVersion();
148
assertNotNull(v1Resources);
149
}
150
```
151
152
### Update Operations
153
154
Handle HTTP PUT requests for full resource updates with optimistic concurrency control.
155
156
```java { .api }
157
/**
158
* Handle resource update requests
159
* Performs full resource replacement with resource version checking
160
* @param request RecordedRequest containing updated resource data
161
* @return MockResponse with updated resource or conflict error
162
*/
163
public MockResponse handleUpdate(RecordedRequest request);
164
```
165
166
**Supported Features:**
167
- Full resource replacement
168
- Resource version conflict detection
169
- Metadata preservation and updates
170
- Status subresource updates
171
- Scale subresource updates
172
- Custom resource updates
173
174
**Usage Examples:**
175
176
```java
177
@Test
178
void testResourceUpdate() {
179
// Create initial resource
180
Pod pod = client.pods().inNamespace("default")
181
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
182
.create();
183
184
// Update the resource
185
pod.getMetadata().getLabels().put("updated", "true");
186
Pod updated = client.pods().inNamespace("default").resource(pod).update();
187
188
assertEquals("true", updated.getMetadata().getLabels().get("updated"));
189
assertNotEquals(pod.getMetadata().getResourceVersion(),
190
updated.getMetadata().getResourceVersion());
191
}
192
193
@Test
194
void testOptimisticConcurrency() {
195
Pod pod = client.pods().inNamespace("default")
196
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
197
.create();
198
199
// Simulate concurrent modification
200
pod.getMetadata().setResourceVersion("999"); // Invalid version
201
202
assertThrows(KubernetesClientException.class, () -> {
203
client.pods().inNamespace("default").resource(pod).update();
204
});
205
}
206
```
207
208
### Patch Operations
209
210
Handle HTTP PATCH requests supporting JSON Patch, Strategic Merge Patch, and JSON Merge Patch.
211
212
```java { .api }
213
/**
214
* Handle resource patch requests
215
* Supports multiple patch formats and partial updates
216
* @param request RecordedRequest containing patch data and content type
217
* @return MockResponse with patched resource or error
218
*/
219
public MockResponse handlePatch(RecordedRequest request);
220
```
221
222
**Supported Patch Types:**
223
- JSON Patch (RFC 6902)
224
- Strategic Merge Patch (Kubernetes-specific)
225
- JSON Merge Patch (RFC 7396)
226
- Server-side Apply patches
227
228
**Usage Examples:**
229
230
```java
231
@Test
232
void testStrategicMergePatch() {
233
// Create pod with initial labels
234
Pod pod = client.pods().inNamespace("default")
235
.resource(new PodBuilder()
236
.withNewMetadata()
237
.withName("test")
238
.addToLabels("env", "dev")
239
.endMetadata()
240
.build())
241
.create();
242
243
// Strategic merge patch to add label
244
Pod patch = new PodBuilder()
245
.withNewMetadata()
246
.withName("test")
247
.addToLabels("app", "web")
248
.endMetadata()
249
.build();
250
251
Pod patched = client.pods().inNamespace("default")
252
.withName("test").patch(PatchContext.of(PatchType.STRATEGIC_MERGE), patch);
253
254
// Both labels should be present
255
assertEquals("dev", patched.getMetadata().getLabels().get("env"));
256
assertEquals("web", patched.getMetadata().getLabels().get("app"));
257
}
258
259
@Test
260
void testJsonPatch() {
261
Pod pod = client.pods().inNamespace("default")
262
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
263
.create();
264
265
// JSON Patch operations
266
String jsonPatch = "[{\"op\":\"add\",\"path\":\"/metadata/labels\",\"value\":{\"patched\":\"true\"}}]";
267
268
Pod patched = client.pods().inNamespace("default")
269
.withName("test").patch(PatchContext.of(PatchType.JSON), jsonPatch);
270
271
assertEquals("true", patched.getMetadata().getLabels().get("patched"));
272
}
273
```
274
275
### Delete Operations
276
277
Handle HTTP DELETE requests with proper finalizer handling and cascading deletion.
278
279
```java { .api }
280
/**
281
* Handle resource deletion requests
282
* Supports finalizers, graceful deletion, and cascading options
283
* @param path Request path identifying the resource to delete
284
* @return MockResponse with deletion confirmation or resource with deletion timestamp
285
*/
286
public MockResponse handleDelete(String path);
287
```
288
289
**Supported Features:**
290
- Immediate deletion for resources without finalizers
291
- Finalizer-based deletion workflow
292
- Graceful deletion periods
293
- Cascade deletion options
294
- Bulk deletion with label selectors
295
296
**Usage Examples:**
297
298
```java
299
@Test
300
void testImmediateDeletion() {
301
Pod pod = client.pods().inNamespace("default")
302
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
303
.create();
304
305
// Delete without finalizers - immediate removal
306
client.pods().inNamespace("default").withName("test").delete();
307
308
Pod deleted = client.pods().inNamespace("default").withName("test").get();
309
assertNull(deleted);
310
}
311
312
@Test
313
void testFinalizerDeletion() {
314
// Create resource with finalizer
315
Pod pod = new PodBuilder()
316
.withNewMetadata()
317
.withName("test")
318
.addToFinalizers("example.com/my-finalizer")
319
.endMetadata()
320
.build();
321
client.pods().inNamespace("default").resource(pod).create();
322
323
// Delete - should mark for deletion but not remove
324
client.pods().inNamespace("default").withName("test").delete();
325
326
Pod marked = client.pods().inNamespace("default").withName("test").get();
327
assertNotNull(marked.getMetadata().getDeletionTimestamp());
328
329
// Remove finalizer to complete deletion
330
marked.getMetadata().getFinalizers().clear();
331
client.pods().inNamespace("default").resource(marked).update();
332
333
// Now it should be gone
334
Pod gone = client.pods().inNamespace("default").withName("test").get();
335
assertNull(gone);
336
}
337
```
338
339
### Watch Operations
340
341
Handle WebSocket watch requests for real-time resource change notifications.
342
343
```java { .api }
344
/**
345
* Handle watch requests for real-time resource monitoring
346
* Establishes WebSocket connection and sends watch events
347
* @param path Request path with watch=true parameter
348
* @return MockResponse with WebSocket upgrade for watch stream
349
*/
350
public MockResponse handleWatch(String path);
351
```
352
353
**Supported Features:**
354
- Resource-specific watches
355
- Namespace-scoped and cluster-scoped watches
356
- Label and field selector filtering
357
- Resume from resource version
358
- Watch event types: ADDED, MODIFIED, DELETED
359
360
**Usage Examples:**
361
362
```java
363
@Test
364
void testResourceWatch() throws InterruptedException {
365
CountDownLatch latch = new CountDownLatch(2);
366
List<WatchEvent<Pod>> events = new ArrayList<>();
367
368
// Start watching pods
369
Watch watch = client.pods().inNamespace("default").watch(new Watcher<Pod>() {
370
@Override
371
public void eventReceived(Action action, Pod resource) {
372
events.add(new WatchEvent<>(action, resource));
373
latch.countDown();
374
}
375
376
@Override
377
public void onClose(WatcherException cause) {}
378
});
379
380
try {
381
// Create a pod - should trigger ADDED event
382
Pod pod = new PodBuilder().withNewMetadata().withName("watched").endMetadata().build();
383
client.pods().inNamespace("default").resource(pod).create();
384
385
// Update the pod - should trigger MODIFIED event
386
pod.getMetadata().getLabels().put("updated", "true");
387
client.pods().inNamespace("default").resource(pod).update();
388
389
// Wait for events
390
assertTrue(latch.await(5, TimeUnit.SECONDS));
391
assertEquals(2, events.size());
392
assertEquals(Watcher.Action.ADDED, events.get(0).getAction());
393
assertEquals(Watcher.Action.MODIFIED, events.get(1).getAction());
394
} finally {
395
watch.close();
396
}
397
}
398
```
399
400
### Mixed Mode Dispatcher
401
402
Combines expectations-based mocking with CRUD operations for flexible testing.
403
404
```java { .api }
405
/**
406
* Composite dispatcher combining MockDispatcher and KubernetesCrudDispatcher
407
* Pre-recorded expectations take priority over CRUD operations
408
*/
409
public class KubernetesMixedDispatcher extends Dispatcher
410
implements Resetable, CustomResourceAware {
411
412
/**
413
* Constructor with responses map for expectations
414
* @param responses Map of request-response expectations
415
*/
416
public KubernetesMixedDispatcher(Map<ServerRequest, Queue<ServerResponse>> responses);
417
418
/**
419
* Constructor with custom resource contexts
420
* @param responses Map of request-response expectations
421
* @param crdContexts List of custom resource definition contexts
422
*/
423
public KubernetesMixedDispatcher(
424
Map<ServerRequest, Queue<ServerResponse>> responses,
425
List<CustomResourceDefinitionContext> crdContexts);
426
427
/**
428
* Dispatch requests to appropriate handler
429
* Checks expectations first, falls back to CRUD operations
430
* @param request RecordedRequest to handle
431
* @return MockResponse from expectations or CRUD handler
432
*/
433
public MockResponse dispatch(RecordedRequest request);
434
}
435
```
436
437
**Usage Examples:**
438
439
```java
440
@EnableKubernetesMockClient(crud = true)
441
class MixedModeTest {
442
KubernetesMockServer server;
443
KubernetesClient client;
444
445
@Test
446
void testMixedMode() {
447
// Set up specific expectation
448
server.expect().get()
449
.withPath("/api/v1/namespaces/kube-system/pods/special-pod")
450
.andReturn(200, new PodBuilder()
451
.withNewMetadata().withName("special-pod").endMetadata()
452
.build())
453
.once();
454
455
// This uses the expectation
456
Pod special = client.pods().inNamespace("kube-system").withName("special-pod").get();
457
assertEquals("special-pod", special.getMetadata().getName());
458
459
// This uses CRUD mode (no expectation set)
460
Pod regular = new PodBuilder().withNewMetadata().withName("regular").endMetadata().build();
461
client.pods().inNamespace("default").resource(regular).create();
462
463
Pod retrieved = client.pods().inNamespace("default").withName("regular").get();
464
assertEquals("regular", retrieved.getMetadata().getName());
465
}
466
}
467
```