|
| 1 | +from typing import List |
1 | 2 | from unittest.mock import MagicMock, patch
|
2 | 3 |
|
3 | 4 | import pytest
|
|
20 | 21 | delete_deployments,
|
21 | 22 | await_no_resources_found,
|
22 | 23 | rescale_deployment,
|
| 24 | + try_creating_custom_objects, |
23 | 25 | )
|
24 | 26 | from zelt.kubernetes.manifest import Manifest
|
25 | 27 |
|
@@ -360,3 +362,183 @@ def test_it_raises_exception(self, delete, waiting):
|
360 | 362 |
|
361 | 363 | delete.assert_called_once()
|
362 | 364 | waiting.assert_not_called()
|
| 365 | + |
| 366 | + |
| 367 | +class TestCreateCustomResources: |
| 368 | + @pytest.fixture() |
| 369 | + def manifests(self) -> List[Manifest]: |
| 370 | + manifest_a = Manifest( |
| 371 | + body={ |
| 372 | + "apiVersion": "zalando.org/v1", |
| 373 | + "kind": "ZalandoCustomResource", |
| 374 | + "metadata": { |
| 375 | + "name": "a_surprise", |
| 376 | + "namespace": "a_namespace", |
| 377 | + "labels": {"application": "an_application"}, |
| 378 | + }, |
| 379 | + } |
| 380 | + ) |
| 381 | + manifest_b = Manifest( |
| 382 | + body={ |
| 383 | + "apiVersion": "example.com/v2", |
| 384 | + "kind": "ExampleCustomResource", |
| 385 | + "metadata": { |
| 386 | + "name": "a_surprise", |
| 387 | + "namespace": "a_namespace", |
| 388 | + "labels": {"application": "an_application"}, |
| 389 | + }, |
| 390 | + } |
| 391 | + ) |
| 392 | + manifest_c = Manifest( |
| 393 | + body={ |
| 394 | + "apiVersion": "example.com/v2", |
| 395 | + "kind": "ClusterCustomResource", |
| 396 | + "metadata": { |
| 397 | + "name": "a_surprise", |
| 398 | + "labels": {"application": "an_application"}, |
| 399 | + }, |
| 400 | + } |
| 401 | + ) |
| 402 | + return [manifest_a, manifest_b, manifest_c] |
| 403 | + |
| 404 | + @pytest.fixture() |
| 405 | + def crds(self) -> List[MagicMock]: |
| 406 | + crd_manifest_a = MagicMock() |
| 407 | + crd_manifest_a.spec.names.kind = "ZalandoCustomResource" |
| 408 | + crd_manifest_a.spec.names.plural = "zalandocustomresources" |
| 409 | + crd_manifest_a.spec.scope = "Namespaced" |
| 410 | + |
| 411 | + crd_manifest_b = MagicMock() |
| 412 | + crd_manifest_b.spec.names.kind = "ExampleCustomResource" |
| 413 | + crd_manifest_b.spec.names.plural = "examplecustomresources" |
| 414 | + crd_manifest_b.spec.scope = "Namespaced" |
| 415 | + |
| 416 | + crd_cluster_manifest = MagicMock() |
| 417 | + crd_cluster_manifest.spec.names.kind = "ClusterCustomResource" |
| 418 | + crd_cluster_manifest.spec.names.plural = "clustercustomresources" |
| 419 | + crd_cluster_manifest.spec.scope = "Cluster" |
| 420 | + |
| 421 | + return [crd_manifest_a, crd_manifest_b, crd_cluster_manifest] |
| 422 | + |
| 423 | + @patch("kubernetes.config.load_kube_config") |
| 424 | + @patch("zelt.kubernetes.client.CustomObjectsApi.create_namespaced_custom_object") |
| 425 | + @patch( |
| 426 | + "zelt.kubernetes.client.ApiextensionsV1beta1Api.list_custom_resource_definition" |
| 427 | + ) |
| 428 | + def test_it_fetches_available_crds_once( |
| 429 | + self, list_crds, create_custom_objects, config, manifests, crds |
| 430 | + ): |
| 431 | + try_creating_custom_objects(manifests) |
| 432 | + list_crds.assert_called_once() |
| 433 | + |
| 434 | + @patch("kubernetes.config.load_kube_config") |
| 435 | + @patch("zelt.kubernetes.client.CustomObjectsApi") |
| 436 | + @patch( |
| 437 | + "zelt.kubernetes.client.ApiextensionsV1beta1Api.list_custom_resource_definition" |
| 438 | + ) |
| 439 | + def test_it_creates_supported_custom_resources( |
| 440 | + self, |
| 441 | + list_crds, |
| 442 | + custom_objects_api, |
| 443 | + config, |
| 444 | + manifests: List[Manifest], |
| 445 | + crds: List[MagicMock], |
| 446 | + ): |
| 447 | + # All 3 CRDs are available in the cluster |
| 448 | + list_crds.return_value = MagicMock(items=crds) |
| 449 | + # Zelt tries to deploy only the namespaced ones |
| 450 | + # (without a CustomClusterResource) |
| 451 | + try_creating_custom_objects(manifests[:2]) |
| 452 | + |
| 453 | + custom_objects_api().create_namespaced_custom_object.assert_any_call( |
| 454 | + namespace=manifests[0].namespace, |
| 455 | + body=manifests[0].body, |
| 456 | + group="zalando.org", |
| 457 | + version="v1", |
| 458 | + plural="zalandocustomresources", |
| 459 | + ) |
| 460 | + custom_objects_api().create_namespaced_custom_object.assert_any_call( |
| 461 | + namespace=manifests[1].namespace, |
| 462 | + body=manifests[1].body, |
| 463 | + group="example.com", |
| 464 | + version="v2", |
| 465 | + plural="examplecustomresources", |
| 466 | + ) |
| 467 | + |
| 468 | + @patch("kubernetes.config.load_kube_config") |
| 469 | + @patch("zelt.kubernetes.client.CustomObjectsApi") |
| 470 | + @patch( |
| 471 | + "zelt.kubernetes.client.ApiextensionsV1beta1Api.list_custom_resource_definition" |
| 472 | + ) |
| 473 | + def test_it_ignores_unsupported_resources( |
| 474 | + self, list_crds, custom_objects_api, config, manifests, crds, caplog |
| 475 | + ): |
| 476 | + # ZalandoCustomResource is the only available CRD in the cluster |
| 477 | + list_crds.return_value = MagicMock(items=[crds[0]]) |
| 478 | + # Zelt tries to deploy ZalandoCustomResource and ExampleCustomResource |
| 479 | + try_creating_custom_objects(manifests) |
| 480 | + |
| 481 | + custom_objects_api().create_namespaced_custom_object.assert_called_once() |
| 482 | + custom_objects_api().create_namespaced_custom_object.assert_called_with( |
| 483 | + namespace=manifests[0].namespace, |
| 484 | + body=manifests[0].body, |
| 485 | + group="zalando.org", |
| 486 | + version="v1", |
| 487 | + plural="zalandocustomresources", |
| 488 | + ) |
| 489 | + assert any( |
| 490 | + "Unsupported custom manifest" in r.msg for r in caplog.records |
| 491 | + ), "an error should be logged" |
| 492 | + |
| 493 | + @patch("kubernetes.config.load_kube_config") |
| 494 | + @patch("zelt.kubernetes.client.CustomObjectsApi") |
| 495 | + @patch( |
| 496 | + "zelt.kubernetes.client.ApiextensionsV1beta1Api.list_custom_resource_definition" |
| 497 | + ) |
| 498 | + def test_it_ignores_non_namespaced_crds( |
| 499 | + self, list_crds, custom_objects_api, config, manifests, crds, caplog |
| 500 | + ): |
| 501 | + # All 3 CRDs are available in the cluster |
| 502 | + list_crds.return_value = MagicMock(items=crds) |
| 503 | + # Zelt tries to deploy a ClusterCustomResource |
| 504 | + try_creating_custom_objects([manifests[2]]) |
| 505 | + |
| 506 | + custom_objects_api().create_namespaced_custom_object.assert_not_called() |
| 507 | + assert any( |
| 508 | + "Non-namespaced resources are not supported" in r.msg |
| 509 | + for r in caplog.records |
| 510 | + ), "an error should be logged" |
| 511 | + |
| 512 | + @patch("kubernetes.config.load_kube_config") |
| 513 | + @patch("zelt.kubernetes.client.CustomObjectsApi") |
| 514 | + @patch( |
| 515 | + "zelt.kubernetes.client.ApiextensionsV1beta1Api.list_custom_resource_definition" |
| 516 | + ) |
| 517 | + def test_it_raises_exception_on_fetching_crds( |
| 518 | + self, list_crds, custom_objects_api, config, manifests |
| 519 | + ): |
| 520 | + list_crds.side_effect = ApiException() |
| 521 | + |
| 522 | + with pytest.raises(ApiException): |
| 523 | + try_creating_custom_objects(manifests) |
| 524 | + |
| 525 | + list_crds.assert_called_once() |
| 526 | + custom_objects_api().create_namespaced_custom_object.assert_not_called() |
| 527 | + |
| 528 | + @patch("kubernetes.config.load_kube_config") |
| 529 | + @patch("zelt.kubernetes.client.CustomObjectsApi") |
| 530 | + @patch( |
| 531 | + "zelt.kubernetes.client.ApiextensionsV1beta1Api.list_custom_resource_definition" |
| 532 | + ) |
| 533 | + def test_it_raises_exception_on_creating_resources( |
| 534 | + self, list_crds, custom_objects_api, config, manifests, crds |
| 535 | + ): |
| 536 | + list_crds.return_value = MagicMock(items=crds) |
| 537 | + custom_objects_api().create_namespaced_custom_object.side_effect = ( |
| 538 | + ApiException() |
| 539 | + ) |
| 540 | + |
| 541 | + with pytest.raises(ApiException): |
| 542 | + try_creating_custom_objects(manifests) |
| 543 | + |
| 544 | + list_crds.assert_called_once() |
0 commit comments